/* eslint-disable */
(function (global, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object') {
    module.exports = global.document
      ? factory(global, true)
      : function (w) {
        if (!w.document) {
          throw new Error('Winfo requires a window with a document')
        }
        return factory(w)
      }
  } else {factory(global)}
})(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
  let h337 = (function () {
    var a = {
      defaultRadius: 40,
      defaultRenderer: 'canvas2d',
      defaultGradient: {
        0.25: 'rgb(0,0,255)',
        0.55: 'rgb(0,255,0)',
        0.85: 'yellow',
        1: 'rgb(255,0,0)'
      },
      defaultMaxOpacity: 1,
      defaultMinOpacity: 0,
      defaultBlur: 0.85,
      defaultXField: 'x',
      defaultYField: 'y',
      defaultValueField: 'value',
      plugins: {}
    }
    var b = (function h () {
      var b = function d (a) {
        this._coordinator = {}
        this._data = []
        this._radi = []
        this._min = 10
        this._max = 1
        this._xField = a['xField'] || a.defaultXField
        this._yField = a['yField'] || a.defaultYField
        this._valueField = a['valueField'] || a.defaultValueField
        if (a['radius']) {this._cfgRadius = a['radius']}
      }
      var c = a.defaultRadius
      b.prototype = {
        _organiseData: function (a, b) {
          var d = a[this._xField]
          var e = a[this._yField]
          var f = this._radi
          var g = this._data
          var h = this._max
          var i = this._min
          var j = a[this._valueField] || 1
          var k = a.radius || this._cfgRadius || c
          if (!g[d]) {
            g[d] = []
            f[d] = []
          }
          if (!g[d][e]) {
            g[d][e] = j
            f[d][e] = k
          } else {g[d][e] += j}
          var l = g[d][e]
          if (l > h) {
            if (!b) {this._max = l} else {this.setDataMax(l)}
            return false
          } else if (l < i) {
            if (!b) {this._min = l} else {this.setDataMin(l)}
            return false
          } else {
            return {
              x: d,
              y: e,
              value: j,
              radius: k,
              min: i,
              max: h
            }
          }
        },
        _unOrganizeData: function () {
          var a = []
          var b = this._data
          var c = this._radi
          for (var d in b) {
            for (var e in b[d]) {
              a.push({
                x: d,
                y: e,
                radius: c[d][e],
                value: b[d][e]
              })
            }
          }
          return {
            min: this._min,
            max: this._max,
            data: a
          }
        },
        _onExtremaChange: function () {
          this._coordinator.emit('extremachange', {
            min: this._min,
            max: this._max
          })
        },
        addData: function () {
          if (arguments[0].length > 0) {
            var a = arguments[0]
            var b = a.length
            while (b--) {this.addData.call(this, a[b])}
          } else {
            var c = this._organiseData(arguments[0], true)
            if (c) {
              if (this._data.length === 0) {this._min = this._max = c.value}
              this._coordinator.emit('renderpartial', {
                min: this._min,
                max: this._max,
                data: [c]
              })
            }
          }
          return this
        },
        setData: function (a) {
          var b = a.data
          var c = b.length
          this._data = []
          this._radi = []
          for (var d = 0; d < c; d++) {this._organiseData(b[d], false)}
          this._max = a.max
          this._min = a.min || 0
          this._onExtremaChange()
          this._coordinator.emit('renderall', this._getInternalData())
          return this
        },
        removeData: function () {},
        setDataMax: function (a) {
          this._max = a
          this._onExtremaChange()
          this._coordinator.emit('renderall', this._getInternalData())
          return this
        },
        setDataMin: function (a) {
          this._min = a
          this._onExtremaChange()
          this._coordinator.emit('renderall', this._getInternalData())
          return this
        },
        setCoordinator: function (a) {this._coordinator = a},
        _getInternalData: function () {
          return {
            max: this._max,
            min: this._min,
            data: this._data,
            radi: this._radi
          }
        },
        getData: function () {return this._unOrganizeData()}
      }
      return b
    }())
    var c = (function i () {
      var a = function (a) {
        var b = a.gradient || a.defaultGradient
        var c = document.createElement('canvas')
        var d = c.getContext('2d')
        c.width = 256
        c.height = 1
        var e = d.createLinearGradient(0, 0, 256, 1)
        for (var f in b) {e.addColorStop(f, b[f])}
        d.fillStyle = e
        d.fillRect(0, 0, 256, 1)
        return d.getImageData(0, 0, 256, 1).data
      }
      var b = function (a, b) {
        var c = document.createElement('canvas')
        var d = c.getContext('2d')
        var e = a
        var f = a
        c.width = c.height = a * 2
        if (b == 1) {
          d.beginPath()
          d.arc(e, f, a, 0, 2 * Math.PI, false)
          d.fillStyle = 'rgba(0,0,0,1)'
          d.fill()
        } else {
          var g = d.createRadialGradient(e, f, a * b, e, f, a)
          g.addColorStop(0, 'rgba(0,0,0,1)')
          g.addColorStop(1, 'rgba(0,0,0,0)')
          d.fillStyle = g
          d.fillRect(0, 0, 2 * a, 2 * a)
        }
        return c
      }
      var c = function (a) {
        var b = []
        var c = a.min
        var d = a.max
        var e = a.radi
        var a = a.data
        var f = Object.keys(a)
        var g = f.length
        while (g--) {
          var h = f[g]
          var i = Object.keys(a[h])
          var j = i.length
          while (j--) {
            var k = i[j]
            var l = a[h][k]
            var m = e[h][k]
            b.push({
              x: h,
              y: k,
              value: l,
              radius: m
            })
          }
        }
        return {
          min: c,
          max: d,
          data: b
        }
      }

      function d (b) {
        var c = b.container
        var d = this.shadowCanvas = document.createElement('canvas')
        var e = this.canvas = b.canvas || document.createElement('canvas')
        var f = this._renderBoundaries = [1e4, 1e4, 0, 0]
        var g = getComputedStyle(b.container) || {}
        e.className = 'heatmap-canvas'
        this._width = e.width = d.width = b.width || +g.width.replace(/px/, '')
        this._height = e.height = d.height = b.height ||
          +g.height.replace(/px/, '')
        this.shadowCtx = d.getContext('2d')
        this.ctx = e.getContext('2d')
        e.style.cssText = d.style.cssText = 'position:absolute;left:0;top:0;'
        c.style.position = 'relative'
        c.appendChild(e)
        this._palette = a(b)
        this._templates = {}
        this._setStyles(b)
      }

      d.prototype = {
        renderPartial: function (a) {
          if (a.data.length > 0) {
            this._drawAlpha(a)
            this._colorize()
          }
        },
        renderAll: function (a) {
          this._clear()
          if (a.data.length > 0) {
            this._drawAlpha(c(a))
            this._colorize()
          }
        },
        _updateGradient: function (b) {this._palette = a(b)},
        updateConfig: function (a) {
          if (a['gradient']) {this._updateGradient(a)}
          this._setStyles(a)
        },
        setDimensions: function (a, b) {
          this._width = a
          this._height = b
          this.canvas.width = this.shadowCanvas.width = a
          this.canvas.height = this.shadowCanvas.height = b
        },
        _clear: function () {
          this.shadowCtx.clearRect(0, 0, this._width, this._height)
          this.ctx.clearRect(0, 0, this._width, this._height)
        },
        _setStyles: function (a) {
          this._blur = a.blur == 0 ? 0 : a.blur || a.defaultBlur
          if (a.backgroundColor) {this.canvas.style.backgroundColor = a.backgroundColor}
          this._width = this.canvas.width = this.shadowCanvas.width = a.width ||
            this._width
          this._height = this.canvas.height = this.shadowCanvas.height = a.height ||
            this._height
          this._opacity = (a.opacity || 0) * 255
          this._maxOpacity = (a.maxOpacity || a.defaultMaxOpacity) * 255
          this._minOpacity = (a.minOpacity || a.defaultMinOpacity) * 255
          this._useGradientOpacity = !!a.useGradientOpacity
        },
        _drawAlpha: function (a) {
          var c = this._min = a.min
          var d = this._max = a.max
          var a = a.data || []
          var e = a.length
          var f = 1 - this._blur
          while (e--) {
            var g = a[e]
            var h = g.x
            var i = g.y
            var j = g.radius
            var k = Math.min(g.value, d)
            var l = h - j
            var m = i - j
            var n = this.shadowCtx
            var o
            if (!this._templates[j]) {
              this._templates[j] = o = b(j, f)
            } else {o = this._templates[j]}
            var p = (k - c) / (d - c)
            n.globalAlpha = p < 0.01 ? 0.01 : p
            n.drawImage(o, l, m)
            if (l < this._renderBoundaries[0]) {this._renderBoundaries[0] = l}
            if (m < this._renderBoundaries[1]) {this._renderBoundaries[1] = m}
            if (l + 2 * j > this._renderBoundaries[2]) {
              this._renderBoundaries[2] = l + 2 * j
            }
            if (m + 2 * j > this._renderBoundaries[3]) {
              this._renderBoundaries[3] = m + 2 * j
            }
          }
        },
        _colorize: function () {
          var a = this._renderBoundaries[0]
          var b = this._renderBoundaries[1]
          var c = this._renderBoundaries[2] - a
          var d = this._renderBoundaries[3] - b
          var e = this._width
          var f = this._height
          var g = this._opacity
          var h = this._maxOpacity
          var i = this._minOpacity
          var j = this._useGradientOpacity
          if (a < 0) {a = 0}
          if (b < 0) {b = 0}
          if (a + c > e) {c = e - a}
          if (b + d > f) {d = f - b}
          var k = this.shadowCtx.getImageData(a, b, c, d)
          var l = k.data
          var m = l.length
          var n = this._palette
          for (var o = 3; o < m; o += 4) {
            var p = l[o]
            var q = p * 4
            if (!q) {continue}
            var r
            if (g > 0) {r = g} else {
              if (p < h) {
                if (p < i) {r = i} else {r = p}
              } else {r = h}
            }
            l[o - 3] = n[q]
            l[o - 2] = n[q + 1]
            l[o - 1] = n[q + 2]
            l[o] = j ? n[q + 3] : r
          }
          this.ctx.putImageData(k, a, b)
          this._renderBoundaries = [1e3, 1e3, 0, 0]
        },
        getValueAt: function (a) {
          var b
          var c = this.shadowCtx
          var d = c.getImageData(a.x, a.y, 1, 1)
          var e = d.data[3]
          var f = this._max
          var g = this._min
          b = Math.abs(f - g) * (e / 255) >> 0
          return b
        },
        getDataURL: function () {return this.canvas.toDataURL()}
      }
      return d
    }())
    var d = (function j () {
      var b = false
      if (a['defaultRenderer'] === 'canvas2d') {b = c}
      return b
    }())
    var e = {
      merge: function () {
        var a = {}
        var b = arguments.length
        for (var c = 0; c < b; c++) {
          var d = arguments[c]
          for (var e in d) {a[e] = d[e]}
        }
        return a
      }
    }
    var f = (function k () {
      var c = (function h () {
        function a () {this.cStore = {}}

        a.prototype = {
          on: function (a, b, c) {
            var d = this.cStore
            if (!d[a]) {d[a] = []}
            d[a].push(function (a) {return b.call(c, a)})
          },
          emit: function (a, b) {
            var c = this.cStore
            if (c[a]) {
              var d = c[a].length
              for (var e = 0; e < d; e++) {
                var f = c[a][e]
                f(b)
              }
            }
          }
        }
        return a
      }())
      var f = function (a) {
        var b = a._renderer
        var c = a._coordinator
        var d = a._store
        c.on('renderpartial', b.renderPartial, b)
        c.on('renderall', b.renderAll, b)
        c.on('extremachange', function (b) {
          a._config.onExtremaChange && a._config.onExtremaChange({
            min: b.min,
            max: b.max,
            gradient: a._config['gradient'] || a._config['defaultGradient']
          })
        })
        d.setCoordinator(c)
      }

      function g () {
        var g = this._config = e.merge(a, arguments[0] || {})
        this._coordinator = new c()
        if (g['plugin']) {
          var h = g['plugin']
          if (!a.plugins[h]) {
            throw new Error(
              'Plugin \'' + h + '\' not found. Maybe it was not registered.')
          } else {
            var i = a.plugins[h]
            this._renderer = new i.renderer(g)
            this._store = new i.store(g)
          }
        } else {
          this._renderer = new d(g)
          this._store = new b(g)
        }
        f(this)
      }

      g.prototype = {
        addData: function () {
          this._store.addData.apply(this._store, arguments)
          return this
        },
        removeData: function () {
          this._store.removeData &&
          this._store.removeData.apply(this._store, arguments)
          return this
        },
        setData: function () {
          this._store.setData.apply(this._store, arguments)
          return this
        },
        setDataMax: function () {
          this._store.setDataMax.apply(this._store, arguments)
          return this
        },
        setDataMin: function () {
          this._store.setDataMin.apply(this._store, arguments)
          return this
        },
        configure: function (a) {
          this._config = e.merge(this._config, a)
          this._renderer.updateConfig(this._config)
          this._coordinator.emit('renderall', this._store._getInternalData())
          return this
        },
        repaint: function () {
          this._coordinator.emit('renderall', this._store._getInternalData())
          return this
        },
        getData: function () {return this._store.getData()},
        getDataURL: function () {return this._renderer.getDataURL()},
        getValueAt: function (a) {
          if (this._store.getValueAt) {
            return this._store.getValueAt(a)
          } else if (this._renderer.getValueAt) {
            return this._renderer.getValueAt(a)
          } else {return null}
        }
      }
      return g
    }())
    var g = {
      create: function (a) {return new f(a)},
      register: function (b, c) {a.plugins[b] = c}
    }
    return g
  })()
  let exports = {}
  let version = '1.6.0'
  let TDTTK = `0de59a2dff2555e94a670e2511996e6e`
  var freeze = Object.freeze
  Object.freeze = function (obj) {return obj}

  function extend (dest) {
    var i, j, len, src
    for (j = 1, len = arguments.length; j < len; j++) {
      src = arguments[j]
      for (i in src) {dest[i] = src[i]}
    }
    return dest
  }

  var create = Object.create || (function () {
    function F () {}

    return function (proto) {
      F.prototype = proto
      return new F()
    }
  })()

  function bind (fn, obj) {
    var slice = Array.prototype.slice
    if (fn.bind) {return fn.bind.apply(fn, slice.call(arguments, 1))}
    var args = slice.call(arguments, 2)
    return function () {
      return fn.apply(obj,
        args.length ? args.concat(slice.call(arguments)) : arguments)
    }
  }

  var lastId = 0

  function stamp (obj) {
    obj._winfo_id = obj._winfo_id || ++lastId
    return obj._winfo_id
  }

  function throttle (fn, time, context) {
    var lock, args, wrapperFn, later
    later = function () {
      lock = false
      if (args) {
        wrapperFn.apply(context, args)
        args = false
      }
    }
    wrapperFn = function () {
      if (lock) {args = arguments} else {
        fn.apply(context, arguments)
        setTimeout(later, time)
        lock = true
      }
    }
    return wrapperFn
  }

  function wrapNum (x, range, includeMax) {
    var max = range[1]
    var min = range[0]
    var d = max - min
    return x === max && includeMax ? x : ((x - min) % d + d) % d + min
  }

  function falseFn () {return false}

  function formatNum (num, digits) {
    var pow = Math.pow(10, (digits === undefined ? 6 : digits))
    return Math.round(num * pow) / pow
  }

  function trim (str) {
    return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '')
  }

  function splitWords (str) {return trim(str).split(/\s+/)}

  function setOptions (obj, options) {
    if (!obj.hasOwnProperty('options')) {
      obj.options = obj.options ? create(obj.options) : {}
    }
    for (var i in options) {obj.options[i] = options[i]}
    return obj.options
  }

  function extendOptions (
    defaultOptions, newOptions) {
    for (var key in newOptions) {
      if (newOptions.hasOwnProperty(key)) {
        var data = newOptions[key]
        if (typeof data === 'string' || typeof data === 'number' ||
          typeof data === 'boolean' || typeof data ===
          'function') {defaultOptions[key] = newOptions[key]} else {
          if (!defaultOptions.hasOwnProperty(
            key)) defaultOptions[key] = newOptions[key]
          extend(defaultOptions[key], newOptions[key])
        }
      }
    }
    return defaultOptions
  }

  function getParamString (obj, existingUrl, uppercase) {
    var params = []
    for (var i in obj) {
      params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' +
        encodeURIComponent(obj[i]))
    }
    return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') +
      params.join('&')
  }

  var templateRe = /\{ *([\w_-]+) *\}/g

  function template (str, data) {
    return str.replace(templateRe, function (str, key) {
      var value = data[key]
      if (value === undefined) {
        throw new Error('No value provided for variable ' + str)
      } else if (typeof value === 'function') {value = value(data)}
      return value
    })
  }

  var isArray = Array.isArray || function (obj) {
    return (Object.prototype.toString.call(obj) === '[object Array]')
  }

  function indexOf (array, el) {
    for (var i = 0; i < array.length; i++) {if (array[i] === el) {return i}}
    return -1
  }

  var emptyImageUrl = ''

  function getPrefixed (name) {
    return window['webkit' + name] || window['moz' + name] ||
      window['ms' + name]
  }

  var lastTime = 0

  function timeoutDefer (fn) {
    var time = +new Date()
    var timeToCall = Math.max(0, 16 - (time - lastTime))
    lastTime = time + timeToCall
    return window.setTimeout(fn, timeToCall)
  }

  var requestFn = window.requestAnimationFrame ||
    getPrefixed('RequestAnimationFrame') || timeoutDefer
  var cancelFn = window.cancelAnimationFrame ||
    getPrefixed('CancelAnimationFrame') ||
    getPrefixed('CancelRequestAnimationFrame') ||
    function (id) {window.clearTimeout(id)}

  function requestAnimFrame (fn, context, immediate) {
    if (immediate && requestFn === timeoutDefer) {
      fn.call(context)
    } else {return requestFn.call(window, bind(fn, context))}
  }

  function cancelAnimFrame (id) {if (id) {cancelFn.call(window, id)}}

  const conversionDegreeAndGeo = {
    GeoToDegree: function (num) {
      let degree, min, sec, temvalue
      degree = parseInt(num)
      temvalue = (num - degree) * 60
      min = parseInt(temvalue)
      sec = Math.ceil((temvalue - min) * 60, 5)
      if (sec == 60) {
        sec = 0
        min = min + 1
      }
      if (min == 60) {
        min = 0
        degree = degree + 1
      }
      return [Math.abs(degree), Math.abs(min), Math.abs(sec)]
    },
    GeoToLngDegree: function (lng) {
      let direction = ''
      let degree = conversionDegreeAndGeo.GeoToDegree(lng)
      if (lng < 0 && lng > -180) {direction = 'W'} else if (lng > 0 && lng <
        180) {direction = 'E'}
      degree.push(direction)
      return degree
    },
    GeoToLatDegree: function (lat) {
      let direction = ''
      let degree = conversionDegreeAndGeo.GeoToDegree(lat)
      if (lat < 0) {direction = 'S'} else if (lat > 0) {direction = 'N'}
      degree.push(direction)
      return degree
    },
    DegreeToGeo: function (degree) {
      let num = parseFloat(parseFloat(degree[0]) +
        ((parseFloat(degree[1]) / 60) + (parseFloat(degree[2]) / 3600)))
      if (degree[3] === 'W' || degree[3] === 'S') {
        return 0 - num
      } else {return num}
    }
  }
  let timeStringToTimeStamp = (
    string, dateTimeSplit, dateSplit, timeSplit) => {
    let dateString = string.split(dateTimeSplit)
    let dateStr = dateString[0].split(dateSplit)
    let timeStr = dateString[1].split(timeSplit)
    let date = new Date(Number(dateStr[0]), Number(dateStr[1]) - 1,
      Number(dateStr[2]), Number(timeStr[0]), Number(timeStr[1]),
      Number(timeStr[2]))
    return Number(date)
  }
  let getDateFomat = (param, timestep) => {
    let time = new Date()
    if (timestep != undefined) {time = new Date(Number(timestep))}
    let Y, M, D, W, H, h, m, s
    Y = time.getFullYear()
    M = time.getMonth() + 1 < 10
      ? '0' + (time.getMonth() + 1)
      : time.getMonth() + 1
    D = time.getDate() < 10 ? '0' + time.getDate() : time.getDate()
    H = time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
    m = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()
    s = time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
    h = time.getHours()
    if (h < 10) {h = '0' + h}
    if (h > 12) {h = (h - 12) + ' pm'} else {h = h + ' am'}
    return param.replace(/Y/g, Y).
    replace(/M/g, M).
    replace(/D/g, D).
    replace(/H/g, H).
    replace(/m/g, m).
    replace(/s/g, s).
    replace(/h/g, h)
  }
  let isPointInPoly = (p, poly) => {
    let px = p.x
    let py = p.y
    let flag = false
    for (let i = 0, l = poly.length, j = l - 1; i < l; j = i, i++) {
      let sx = poly[i].x
      let sy = poly[i].y
      let tx = poly[j].x
      let ty = poly[j].y
      if ((sx === px && sy === py) || (tx === px && ty === py)) {return true}
      if ((sy < py && ty >= py) || (sy >= py && ty < py)) {
        let x = sx + (py - sy) * (tx - sx) / (ty - sy)
        if (x === px) {return true}
        if (x > px) {flag = !flag}
      }
    }
    return flag
  }
  let rotatePoint = (center, point, rotate) => {
    let x2 = (point.x - center.x) * Math.cos(rotate * Math.PI / 180) -
      (point.y - center.y) * Math.sin(rotate * Math.PI / 180) + center.x
    let y2 = (point.x - center.x) * Math.sin(rotate * Math.PI / 180) +
      (point.y - center.y) * Math.cos(rotate * Math.PI / 180) + center.y
    return {
      x: x2,
      y: y2
    }

    // let x2 = (point.x - center.x) * Math.cos(-rotate * Math.PI / 180) +
    //   (point.y - center.y) * Math.sin(rotate * Math.PI / 180) + center.x;
    // let y2 = (point.y - center.y) * Math.cos(-rotate * Math.PI / 180) -
    //   (point.x - center.x) * Math.sin(rotate * Math.PI / 180) + center.y;
    // return {
    //   x: x2,
    //   y: y2
    // };
  }
  let getTwoPointDir = (p0, p1) => {
    let startPoint = new Point(p0.lng, p0.lat)
    let endPoint = new Point(p1.lng, p1.lat)
    let length = startPoint.distanceTo(endPoint)
    if (length <= 0) return 0
    let sinx = (endPoint.y - startPoint.y) / length
    let dir = endPoint.x >= startPoint.x ? (Math.PI * 0.5 - Math.asin(sinx)) *
      180 / Math.PI : (Math.PI * 1.5 + Math.asin(sinx)) * 180 / Math.PI
    return dir
  }
  /** 一次性加载多张图片
   * imgArr 图片路径数组集合  ['./a.png','./b.png']
   * **/
  let loadImgAll = (imgArr) => {
    return new Promise((resolve) => {
      let request = []
      imgArr.map(d => {
        request.push(new Promise((resolve) => {
          let icon = new Image()
          icon.src = d
          icon.onload = () => {resolve(icon)}
        }))
      })
      Promise.all(request).then(res => {resolve(res)})
    })
  }
  var Util = (Object.freeze || Object)({
    freeze: freeze,
    extend: extend,
    create: create,
    bind: bind,
    lastId: lastId,
    stamp: stamp,
    throttle: throttle,
    wrapNum: wrapNum,
    falseFn: falseFn,
    formatNum: formatNum,
    trim: trim,
    splitWords: splitWords,
    setOptions: setOptions,
    getParamString: getParamString,
    template: template,
    isArray: isArray,
    indexOf: indexOf,
    emptyImageUrl: emptyImageUrl,
    requestFn: requestFn,
    cancelFn: cancelFn,
    requestAnimFrame: requestAnimFrame,
    cancelAnimFrame: cancelAnimFrame,
    conversionDegreeAndGeo: conversionDegreeAndGeo,
    getDateFomat: getDateFomat,
    timeStringToTimeStamp,
    isPointInPoly,
    rotatePoint,
    getTwoPointDir,
    loadImgAll
  })

  function Class () {}

  Class.extend = function (props) {
    var NewClass = function () {
      if (this.initialize) {
        this.initialize.apply(this, arguments)
      }
      this.callInitHooks()
    }
    var parentProto = NewClass.__super__ = this.prototype
    var proto = create(parentProto)
    proto.constructor = NewClass
    NewClass.prototype = proto
    for (var i in this) {
      if (this.hasOwnProperty(i) && i !== 'prototype' && i !==
        '__super__') {NewClass[i] = this[i]}
    }
    if (props.statics) {
      extend(NewClass, props.statics)
      delete props.statics
    }
    if (props.includes) {
      checkDeprecatedMixinEvents(props.includes)
      extend.apply(null, [proto].concat(props.includes))
      delete props.includes
    }
    if (proto.options) {
      props.options = extend(create(proto.options), props.options)
    }
    extend(proto, props)
    proto._initHooks = []
    proto.callInitHooks = function () {
      if (this._initHooksCalled) {return}
      if (parentProto.callInitHooks) {parentProto.callInitHooks.call(this)}
      this._initHooksCalled = true
      for (var i = 0, len = proto._initHooks.length; i <
      len; i++) {proto._initHooks[i].call(this)}
    }
    return NewClass
  }
  Class.include = function (props) {
    extend(this.prototype, props)
    return this
  }
  Class.mergeOptions = function (options) {
    extend(this.prototype.options, options)
    return this
  }
  Class.addInitHook = function (fn) {
    var args = Array.prototype.slice.call(arguments, 1)
    var init = typeof fn === 'function' ? fn : function () {
      this[fn].apply(this, args)
    }
    this.prototype._initHooks = this.prototype._initHooks || []
    this.prototype._initHooks.push(init)
    return this
  }

  function checkDeprecatedMixinEvents (includes) {
    includes = isArray(includes)
      ? includes
      : [includes]
    for (var i = 0; i < includes.length; i++) {
      if (includes[i] === Mixin.Events) {
        console.warn('Deprecated include of Mixin.Events: ' +
          'this property will be removed in future releases, ' +
          'please inherit from Evented instead.', new Error().stack)
      }
    }
  }

  var Events = {
    on: function (types, fn, context) {
      if (typeof types === 'object') {
        for (var type in types) {
          this._on(type, types[type], fn)
        }
      } else {
        types = splitWords(types)
        for (var i = 0, len = types.length; i < len; i++) {
          this._on(types[i], fn, context)
        }
      }
      return this
    },
    off: function (types, fn, context) {
      if (!types) {delete this._events} else if (typeof types === 'object') {
        for (var type in types) {
          this._off(type, types[type], fn)
        }
      } else {
        types = splitWords(types)
        for (var i = 0, len = types.length; i < len; i++) {
          this._off(types[i], fn, context)
        }
      }
      return this
    },
    _on: function (type, fn, context) {
      this._events = this._events || {}
      var typeListeners = this._events[type]
      if (!typeListeners) {
        typeListeners = []
        this._events[type] = typeListeners
      }
      if (context === this) {context = undefined}
      var newListener = {
        fn: fn,
        ctx: context
      }
      var listeners = typeListeners
      for (var i = 0, len = listeners.length; i < len; i++) {
        if (listeners[i].fn === fn && listeners[i].ctx === context) {return}
      }
      listeners.push(newListener)
    },
    _off: function (type, fn, context) {
      var listeners, i, len
      if (!this._events) {return}
      listeners = this._events[type]
      if (!listeners) {return}
      if (!fn) {
        for (i = 0, len = listeners.length; i <
        len; i++) {listeners[i].fn = falseFn}
        delete this._events[type]
        return
      }
      if (context === this) {context = undefined}
      if (listeners) {
        for (i = 0, len = listeners.length; i < len; i++) {
          var l = listeners[i]
          if (l.ctx !== context) {continue}
          if (l.fn === fn) {
            l.fn = falseFn
            if (this._firingCount) {this._events[type] = listeners = listeners.slice()}
            listeners.splice(i, 1)
            return
          }
        }
      }
    },
    fire: function (type, data, propagate) {
      if (!this.listens(type, propagate)) {return this}
      var event = extend({}, data, {
        type: type,
        target: this,
        sourceTarget: data && data.sourceTarget || this
      })
      if (this._events) {
        var listeners = this._events[type]
        if (listeners) {
          this._firingCount = (this._firingCount + 1) || 1
          for (var i = 0, len = listeners.length; i < len; i++) {
            var l = listeners[i]
            l.fn.call(l.ctx || this, event)
          }
          this._firingCount--
        }
      }
      if (propagate) {this._propagateEvent(event)}
      return this
    },
    listens: function (type, propagate) {
      var listeners = this._events && this._events[type]
      if (listeners && listeners.length) {return true}
      if (propagate) {
        for (var id in this._eventParents) {
          if (this._eventParents[id].listens(type, propagate)) {return true}
        }
      }
      return false
    },
    once: function (types, fn, context) {
      if (typeof types === 'object') {
        for (var type in types) {
          this.once(type, types[type], fn)
        }
        return this
      }
      var handler = bind(function () {
        this.off(types, fn, context).
        off(types, handler, context)
      }, this)
      return this.on(types, fn, context).on(types, handler, context)
    },
    addEventParent: function (obj) {
      this._eventParents = this._eventParents || {}
      this._eventParents[stamp(obj)] = obj
      return this
    },
    removeEventParent: function (obj) {
      if (this._eventParents) {
        delete this._eventParents[stamp(obj)]
      }
      return this
    },
    _propagateEvent: function (e) {
      for (var id in this._eventParents) {
        this._eventParents[id].fire(e.type, extend({
          layer: e.target,
          propagatedFrom: e.target
        }, e), true)
      }
    }
  }
  Events.addEventListener = Events.on
  Events.removeEventListener = Events.clearAllEventListeners = Events.off
  Events.addOneTimeEventListener = Events.once
  Events.fireEvent = Events.fire
  Events.hasEventListeners = Events.listens
  var Evented = Class.extend(Events)

  function Point (x, y, round) {
    this.x = (round ? Math.round(x) : x)
    this.y = (round ? Math.round(y) : y)
  }

  var trunc = Math.trunc ||
    function (v) {return v > 0 ? Math.floor(v) : Math.ceil(v)}
  Point.prototype = {
    clone: function () {return new Point(this.x, this.y)},
    add: function (point) {return this.clone()._add(toPoint(point))},
    _add: function (point) {
      this.x += point.x
      this.y += point.y
      return this
    },
    subtract: function (point) {return this.clone()._subtract(toPoint(point))},
    _subtract: function (point) {
      this.x -= point.x
      this.y -= point.y
      return this
    },
    divideBy: function (num) {return this.clone()._divideBy(num)},
    _divideBy: function (num) {
      this.x /= num
      this.y /= num
      return this
    },
    multiplyBy: function (num) {return this.clone()._multiplyBy(num)},
    _multiplyBy: function (num) {
      this.x *= num
      this.y *= num
      return this
    },
    scaleBy: function (point) {
      return new Point(this.x * point.x, this.y * point.y)
    },
    unscaleBy: function (point) {
      return new Point(this.x / point.x, this.y / point.y)
    },
    round: function () {return this.clone()._round()},
    _round: function () {
      this.x = Math.round(this.x)
      this.y = Math.round(this.y)
      return this
    },
    floor: function () {return this.clone()._floor()},
    _floor: function () {
      this.x = Math.floor(this.x)
      this.y = Math.floor(this.y)
      return this
    },
    ceil: function () {return this.clone()._ceil()},
    _ceil: function () {
      this.x = Math.ceil(this.x)
      this.y = Math.ceil(this.y)
      return this
    },
    trunc: function () {return this.clone()._trunc()},
    _trunc: function () {
      this.x = trunc(this.x)
      this.y = trunc(this.y)
      return this
    },
    distanceTo: function (point) {
      point = toPoint(point)
      var x = point.x - this.x
      var y = point.y - this.y
      return Math.sqrt(x * x + y * y)
    },
    equals: function (point) {
      point = toPoint(point)
      return point.x === this.x && point.y === this.y
    },
    contains: function (point) {
      point = toPoint(point)
      return Math.abs(point.x) <= Math.abs(this.x) && Math.abs(point.y) <=
        Math.abs(this.y)
    },
    toString: function () {
      return 'Point(' + formatNum(this.x) + ', ' + formatNum(this.y) + ')'
    }
  }

  function toPoint (x, y, round) {
    if (x instanceof Point) {return x}
    if (isArray(x)) {return new Point(x[0], x[1])}
    if (x === undefined || x === null) {return x}
    if (typeof x === 'object' && 'x' in x && 'y' in x) {
      return new Point(x.x, x.y)
    }
    return new Point(x, y, round)
  }

  function Bounds (a, b) {
    if (!a) {return}
    var points = b ? [a, b] : a
    for (var i = 0, len = points.length; i < len; i++) {this.extend(points[i])}
  }

  Bounds.prototype = {
    extend: function (point) {
      point = toPoint(point)
      if (!this.min && !this.max) {
        this.min = point.clone()
        this.max = point.clone()
      } else {
        this.min.x = Math.min(point.x, this.min.x)
        this.max.x = Math.max(point.x, this.max.x)
        this.min.y = Math.min(point.y, this.min.y)
        this.max.y = Math.max(point.y, this.max.y)
      }
      return this
    },
    getCenter: function (round) {
      return new Point((this.min.x + this.max.x) / 2,
        (this.min.y + this.max.y) / 2, round)
    },
    getBottomLeft: function () {return new Point(this.min.x, this.max.y)},
    getTopRight: function () {return new Point(this.max.x, this.min.y)},
    getTopLeft: function () {return this.min},
    getBottomRight: function () {return this.max},
    getSize: function () {return this.max.subtract(this.min)},
    contains: function (obj) {
      var min, max
      if (typeof obj[0] === 'number' || obj instanceof Point) {
        obj = toPoint(obj)
      } else {obj = toBounds(obj)}
      if (obj instanceof Bounds) {
        min = obj.min
        max = obj.max
      } else {min = max = obj}
      return (min.x >= this.min.x) && (max.x <= this.max.x) &&
        (min.y >= this.min.y) && (max.y <= this.max.y)
    },
    intersects: function (bounds) {
      bounds = toBounds(bounds)
      var min = this.min
      var max = this.max
      var min2 = bounds.min
      var max2 = bounds.max
      var xIntersects = (max2.x >= min.x) && (min2.x <= max.x)
      var yIntersects = (max2.y >= min.y) && (min2.y <= max.y)
      return xIntersects && yIntersects
    },
    overlaps: function (bounds) {
      bounds = toBounds(bounds)
      var min = this.min
      var max = this.max
      var min2 = bounds.min
      var max2 = bounds.max
      var xOverlaps = (max2.x > min.x) && (min2.x < max.x)
      var yOverlaps = (max2.y > min.y) && (min2.y < max.y)
      return xOverlaps && yOverlaps
    },
    isValid: function () {return !!(this.min && this.max)}
  }

  function toBounds (a, b) {
    if (!a || a instanceof Bounds) {return a}
    return new Bounds(a, b)
  }

  function LatLngBounds (corner1, corner2) {
    if (!corner1) {return}
    var latlngs = corner2 ? [corner1, corner2] : corner1
    for (var i = 0, len = latlngs.length; i < len; i++) {
      this.extend(latlngs[i])
    }
  }

  LatLngBounds.prototype = {
    extend: function (obj) {
      var sw = this._southWest
      var ne = this._northEast
      var sw2
      var ne2
      if (obj instanceof LatLng) {
        sw2 = obj
        ne2 = obj
      } else if (obj instanceof LatLngBounds) {
        sw2 = obj._southWest
        ne2 = obj._northEast
        if (!sw2 || !ne2) {return this}
      } else {
        return obj
          ? this.extend(toLatLng(obj) || toLatLngBounds(obj))
          : this
      }
      if (!sw && !ne) {
        this._southWest = new LatLng(sw2.lat, sw2.lng)
        this._northEast = new LatLng(ne2.lat, ne2.lng)
      } else {
        sw.lat = Math.min(sw2.lat, sw.lat)
        sw.lng = Math.min(sw2.lng, sw.lng)
        ne.lat = Math.max(ne2.lat, ne.lat)
        ne.lng = Math.max(ne2.lng, ne.lng)
      }
      return this
    },
    pad: function (bufferRatio) {
      var sw = this._southWest
      var ne = this._northEast
      var heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio
      var widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio
      return new LatLngBounds(
        new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
        new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer))
    },
    getCenter: function () {
      return new LatLng((this._southWest.lat + this._northEast.lat) / 2,
        (this._southWest.lng + this._northEast.lng) / 2)
    },
    getSouthWest: function () {return this._southWest},
    getNorthEast: function () {return this._northEast},
    getNorthWest: function () {
      return new LatLng(this.getNorth(), this.getWest())
    },
    getSouthEast: function () {
      return new LatLng(this.getSouth(), this.getEast())
    },
    getWest: function () {return this._southWest.lng},
    getSouth: function () {return this._southWest.lat},
    getEast: function () {return this._northEast.lng},
    getNorth: function () {return this._northEast.lat},
    contains: function (obj) {
      if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in
        obj) {obj = toLatLng(obj)} else {obj = toLatLngBounds(obj)}
      var sw = this._southWest
      var ne = this._northEast
      var sw2
      var ne2
      if (obj instanceof LatLngBounds) {
        sw2 = obj.getSouthWest()
        ne2 = obj.getNorthEast()
      } else {sw2 = ne2 = obj}
      return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng)
    },
    intersects: function (bounds) {
      bounds = toLatLngBounds(bounds)
      var sw = this._southWest
      var ne = this._northEast
      var sw2 = bounds.getSouthWest()
      var ne2 = bounds.getNorthEast()
      var latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat)
      var lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng)
      return latIntersects && lngIntersects
    },
    overlaps: function (bounds) {
      bounds = toLatLngBounds(bounds)
      var sw = this._southWest
      var ne = this._northEast
      var sw2 = bounds.getSouthWest()
      var ne2 = bounds.getNorthEast()
      var latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat)
      var lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng)
      return latOverlaps && lngOverlaps
    },
    toBBoxString: function () {
      return [
        this.getWest(),
        this.getSouth(),
        this.getEast(),
        this.getNorth()
      ].join(',')
    },
    equals: function (bounds, maxMargin) {
      if (!bounds) {return false}
      bounds = toLatLngBounds(bounds)
      return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
        this._northEast.equals(bounds.getNorthEast(), maxMargin)
    },
    isValid: function () {return !!(this._southWest && this._northEast)}
  }

  function toLatLngBounds (a, b) {
    if (a instanceof LatLngBounds) {return a}
    return new LatLngBounds(a, b)
  }

  function LatLng (lat, lng, alt) {
    if (isNaN(lat) || isNaN(lng)) {
      throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')')
    }
    this.lat = +lat
    this.lng = +lng
    if (alt !== undefined) {this.alt = +alt}
  }

  LatLng.prototype = {
    equals: function (obj, maxMargin) {
      if (!obj) {return false}
      obj = toLatLng(obj)
      var margin = Math.max(Math.abs(this.lat - obj.lat),
        Math.abs(this.lng - obj.lng))
      return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin)
    },
    toString: function (precision) {
      return 'LatLng(' + formatNum(this.lat, precision) + ', ' +
        formatNum(this.lng, precision) + ')'
    },
    distanceTo: function (other) {
      return Earth.distance(this, toLatLng(other))
    },
    wrap: function () {return Earth.wrapLatLng(this)},
    toBounds: function (sizeInMeters) {
      var latAccuracy = 180 * sizeInMeters / 40075017
      var lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat)
      return toLatLngBounds([this.lat - latAccuracy, this.lng - lngAccuracy],
        [this.lat + latAccuracy, this.lng + lngAccuracy])
    },
    clone: function () {return new LatLng(this.lat, this.lng, this.alt)}
  }

  function toLatLng (a, b, c) {
    if (a instanceof LatLng) {return a}
    if (isArray(a) && typeof a[0] !== 'object') {
      if (a.length === 3) {return new LatLng(a[0], a[1], a[2])}
      if (a.length === 2) {return new LatLng(a[0], a[1])}
      return null
    }
    if (a === undefined || a === null) {return a}
    if (typeof a === 'object' && 'lat' in a) {
      return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt)
    }
    if (b === undefined) {return null}
    return new LatLng(a, b, c)
  }

  var CRS = {
    latLngToPoint: function (
      latlng, zoom) {
      var projectedPoint = this.projection.project(latlng)
      var scale = this.scale(zoom)
      return this.transformation._transform(projectedPoint, scale)
    },
    pointToLatLng: function (point, zoom) {
      var scale = this.scale(zoom)
      var untransformedPoint = this.transformation.untransform(point, scale)
      return this.projection.unproject(untransformedPoint)
    },
    project: function (latlng) {return this.projection.project(latlng)},
    unproject: function (point) {return this.projection.unproject(point)},
    scale: function (zoom) {return 256 * Math.pow(2, zoom)},
    zoom: function (scale) {return Math.log(scale / 256) / Math.LN2},
    getProjectedBounds: function (zoom) {
      if (this.infinite) {return null}
      var b = this.projection.bounds
      var s = this.scale(zoom)
      var min = this.transformation.transform(b.min, s)
      var max = this.transformation.transform(b.max, s)
      return new Bounds(min, max)
    },
    infinite: false,
    wrapLatLng: function (latlng) {
      var lng = this.wrapLng
        ? wrapNum(latlng.lng, this.wrapLng, true)
        : latlng.lng
      var lat = this.wrapLat
        ? wrapNum(latlng.lat, this.wrapLat, true)
        : latlng.lat
      var alt = latlng.alt
      return new LatLng(lat, lng, alt)
    },
    wrapLatLngBounds: function (bounds) {
      var center = bounds.getCenter()
      var newCenter = this.wrapLatLng(center)
      var latShift = center.lat - newCenter.lat
      var lngShift = center.lng - newCenter.lng
      if (latShift === 0 && lngShift === 0) {return bounds}
      var sw = bounds.getSouthWest()
      var ne = bounds.getNorthEast()
      var newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift)
      var newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift)
      return new LatLngBounds(newSw, newNe)
    }
  }
  var Earth = extend({}, CRS, {
    wrapLng: [-180, 180],
    R: 6371000,
    distance: function (latlng1, latlng2) {
      var rad = Math.PI / 180
      var lat1 = latlng1.lat * rad
      var lat2 = latlng2.lat * rad
      var sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2)
      var sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2)
      var a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon *
        sinDLon
      var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
      return this.R * c
    }
  })
  var earthRadius = 6378137
  var SphericalMercator = {
    R: earthRadius,
    MAX_LATITUDE: 85.0511287798,
    project: function (latlng) {
      var d = Math.PI / 180
      var max = this.MAX_LATITUDE
      var lat = Math.max(Math.min(max, latlng.lat), -max)
      var sin = Math.sin(lat * d)
      return new Point(this.R * latlng.lng * d,
        this.R * Math.log((1 + sin) / (1 - sin)) / 2)
    },
    unproject: function (point) {
      var d = 180 / Math.PI
      return new LatLng(
        (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
        point.x * d / this.R)
    },
    bounds: (function () {
      var d = earthRadius * Math.PI
      return new Bounds([-d, -d], [d, d])
    })()
  }

  function Transformation (a, b, c, d) {
    if (isArray(a)) {
      this._a = a[0]
      this._b = a[1]
      this._c = a[2]
      this._d = a[3]
      return
    }
    this._a = a
    this._b = b
    this._c = c
    this._d = d
  }

  Transformation.prototype = {
    transform: function (
      point, scale) {
      return this._transform(point.clone(), scale)
    },
    _transform: function (point, scale) {
      scale = scale || 1
      point.x = scale * (this._a * point.x + this._b)
      point.y = scale * (this._c * point.y + this._d)
      return point
    },
    untransform: function (point, scale) {
      scale = scale || 1
      return new Point((point.x / scale - this._b) / this._a,
        (point.y / scale - this._d) / this._c)
    }
  }

  function toTransformation (a, b, c, d) {
    return new Transformation(a, b, c, d)
  }

  var EPSG3857 = extend({}, Earth, {
    code: 'EPSG:3857',
    projection: SphericalMercator,
    transformation: (function () {
      var scale = 0.5 / (Math.PI * SphericalMercator.R)
      return toTransformation(scale, 0.5, -scale, 0.5)
    }())
  })
  var EPSG900913 = extend({}, EPSG3857, { code: 'EPSG:900913' })

  function svgCreate (name) {
    return document.createElementNS('http://www.w3.org/2000/svg', name)
  }

  function pointsToPath (rings, closed) {
    var str = ''
    var i
    var j
    var len
    var len2
    var points
    var p
    for (i = 0, len = rings.length; i < len; i++) {
      points = rings[i]
      for (j = 0, len2 = points.length; j < len2; j++) {
        p = points[j]
        str += (j ? 'L' : 'M') + p.x + ' ' + p.y
      }
      str += closed ? (svg ? 'z' : 'x') : ''
    }
    return str || 'M0 0'
  }

  var style$1 = document.documentElement.style
  var ie = 'ActiveXObject' in window
  var ielt9 = ie && !document.addEventListener
  var edge = 'msLaunchUri' in navigator && !('documentMode' in document)
  var webkit = userAgentContains('webkit')
  var android = userAgentContains('android')
  var android23 = userAgentContains('android 2') ||
    userAgentContains('android 3')
  var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1],
    10)
  var androidStock = android && userAgentContains('Google') && webkitVer <
    537 && !('AudioNode' in window)
  var opera = !!window.opera
  var chrome = userAgentContains('chrome')
  var gecko = userAgentContains('gecko') && !webkit && !opera && !ie
  var safari = !chrome && userAgentContains('safari')
  var phantom = userAgentContains('phantom')
  var opera12 = 'OTransition' in style$1
  var win = navigator.platform.indexOf('Win') === 0
  var ie3d = ie && ('transition' in style$1)
  var webkit3d = ('WebKitCSSMatrix' in window) &&
    ('m11' in new window.WebKitCSSMatrix()) && !android23
  var gecko3d = 'MozPerspective' in style$1
  var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) &&
    !opera12 && !phantom
  var mobile = typeof orientation !== 'undefined' ||
    userAgentContains('mobile')
  var mobileWebkit = mobile && webkit
  var mobileWebkit3d = mobile && webkit3d
  var msPointer = !window.PointerEvent && window.MSPointerEvent
  var pointer = !webkit && !!(window.PointerEvent || msPointer)
  var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
    (window.DocumentTouch && document instanceof window.DocumentTouch))
  var mobileOpera = mobile && opera
  var mobileGecko = mobile && gecko
  var retina = (window.devicePixelRatio ||
    (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
  var passiveEvents = function () {
    var supportsPassiveOption = false
    try {
      var opts = Object.defineProperty({}, 'passive',
        { get: function () {supportsPassiveOption = true} })
      window.addEventListener('testPassiveEventSupport', falseFn, opts)
      window.removeEventListener('testPassiveEventSupport', falseFn, opts)
    } catch (e) {}
    return supportsPassiveOption
  }
  var canvas = (function () {
    return !!document.createElement('canvas').getContext
  }())
  var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect)
  var vml = !svg && (function () {
    try {
      var div = document.createElement('div')
      div.innerHTML = '<v:shape adj="1"/>'
      var shape = div.firstChild
      shape.style.behavior = 'url(#default#VML)'
      return shape && (typeof shape.adj === 'object')
    } catch (e) {return false}
  }())

  function userAgentContains (str) {
    return navigator.userAgent.toLowerCase().
    indexOf(str) >= 0
  }

  var Browser = (Object.freeze || Object)({
    ie: ie,
    ielt9: ielt9,
    edge: edge,
    webkit: webkit,
    android: android,
    android23: android23,
    androidStock: androidStock,
    opera: opera,
    chrome: chrome,
    gecko: gecko,
    safari: safari,
    phantom: phantom,
    opera12: opera12,
    win: win,
    ie3d: ie3d,
    webkit3d: webkit3d,
    gecko3d: gecko3d,
    any3d: any3d,
    mobile: mobile,
    mobileWebkit: mobileWebkit,
    mobileWebkit3d: mobileWebkit3d,
    msPointer: msPointer,
    pointer: pointer,
    touch: touch,
    mobileOpera: mobileOpera,
    mobileGecko: mobileGecko,
    retina: retina,
    passiveEvents: passiveEvents,
    canvas: canvas,
    svg: svg,
    vml: vml
  })
  var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown'
  var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove'
  var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup'
  var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel'
  var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION']
  var _pointers = {}
  var _pointerDocListener = false
  var _pointersCount = 0

  function addPointerListener (obj, type, handler, id) {
    if (type === 'touchstart') {
      _addPointerStart(obj, handler, id)
    } else if (type === 'touchmove') {
      _addPointerMove(obj, handler, id)
    } else if (type === 'touchend') {_addPointerEnd(obj, handler, id)}
    return this
  }

  function removePointerListener (obj, type, id) {
    var handler = obj['_winfo_' + type + id]
    if (type === 'touchstart') {
      obj.removeEventListener(POINTER_DOWN, handler, false)
    } else if (type === 'touchmove') {
      obj.removeEventListener(POINTER_MOVE, handler, false)
    } else if (type === 'touchend') {
      obj.removeEventListener(POINTER_UP, handler, false)
      obj.removeEventListener(POINTER_CANCEL, handler, false)
    }
    return this
  }

  function _addPointerStart (obj, handler, id) {
    var onDown = bind(function (e) {
      if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE &&
        e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
        if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
          preventDefault(e)
        } else {return}
      }
      _handlePointer(e, handler)
    })
    obj['_winfo_touchstart' + id] = onDown
    obj.addEventListener(POINTER_DOWN, onDown, false)
    if (!_pointerDocListener) {
      document.documentElement.addEventListener(POINTER_DOWN,
        _globalPointerDown, true)
      document.documentElement.addEventListener(POINTER_MOVE,
        _globalPointerMove, true)
      document.documentElement.addEventListener(POINTER_UP, _globalPointerUp,
        true)
      document.documentElement.addEventListener(POINTER_CANCEL,
        _globalPointerUp, true)
      _pointerDocListener = true
    }
  }

  function _globalPointerDown (e) {
    _pointers[e.pointerId] = e
    _pointersCount++
  }

  function _globalPointerMove (e) {if (_pointers[e.pointerId]) {_pointers[e.pointerId] = e}}

  function _globalPointerUp (e) {
    delete _pointers[e.pointerId]
    _pointersCount--
  }

  function _handlePointer (e, handler) {
    e.touches = []
    for (var i in _pointers) {e.touches.push(_pointers[i])}
    e.changedTouches = [e]
    handler(e)
  }

  function _addPointerMove (
    obj, handler, id) {
    var onMove = function (e) {
      if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType ===
        'mouse') && e.buttons === 0) {return}
      _handlePointer(e, handler)
    }
    obj['_winfo_touchmove' + id] = onMove
    obj.addEventListener(POINTER_MOVE, onMove, false)
  }

  function _addPointerEnd (
    obj, handler, id) {
    var onUp = function (e) {
      _handlePointer(e, handler)
    }
    obj['_winfo_touchend' + id] = onUp
    obj.addEventListener(POINTER_UP, onUp, false)
    obj.addEventListener(POINTER_CANCEL, onUp, false)
  }

  var _touchstart = msPointer ? 'MSPointerDown' : pointer
    ? 'pointerdown'
    : 'touchstart'
  var _touchend = msPointer ? 'MSPointerUp' : pointer
    ? 'pointerup'
    : 'touchend'
  var _pre = '_winfo_'

  function addDoubleTapListener (obj, handler, id) {
    var last
    var touch$$1
    var doubleTap = false
    var delay = 250

    function onTouchStart (e) {
      var count
      if (pointer) {
        if ((!edge) || e.pointerType === 'mouse') {return}
        count = _pointersCount
      } else {count = e.touches.length}
      if (count > 1) {return}
      var now = Date.now()
      var delta = now - (last || now)
      touch$$1 = e.touches ? e.touches[0] : e
      doubleTap = (delta > 0 && delta <= delay)
      last = now
    }

    function onTouchEnd (e) {
      if (doubleTap && !touch$$1.cancelBubble) {
        if (pointer) {
          if ((!edge) || e.pointerType === 'mouse') {return}
          var newTouch = {}
          var prop
          var i
          for (i in touch$$1) {
            prop = touch$$1[i]
            newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop
          }
          touch$$1 = newTouch
        }
        touch$$1.type = 'dblclick'
        touch$$1.button = 0
        handler(touch$$1)
        last = null
      }
    }

    obj[_pre + _touchstart + id] = onTouchStart
    obj[_pre + _touchend + id] = onTouchEnd
    obj[_pre + 'dblclick' + id] = handler
    obj.addEventListener(_touchstart, onTouchStart,
      passiveEvents ? { passive: false } : false)
    obj.addEventListener(_touchend, onTouchEnd,
      passiveEvents ? { passive: false } : false)
    obj.addEventListener('dblclick', handler, false)
    return this
  }

  function removeDoubleTapListener (obj, id) {
    var touchstart = obj[_pre + _touchstart + id]
    var touchend = obj[_pre + _touchend + id]
    var dblclick = obj[_pre + 'dblclick' + id]
    obj.removeEventListener(_touchstart, touchstart,
      passiveEvents ? { passive: false } : false)
    obj.removeEventListener(_touchend, touchend,
      passiveEvents ? { passive: false } : false)
    if (!edge) {obj.removeEventListener('dblclick', dblclick, false)}
    return this
  }

  var TRANSFORM = testProp([
    'transform',
    'webkitTransform',
    'OTransform',
    'MozTransform',
    'msTransform'
  ])
  var TRANSITION = testProp([
    'webkitTransition',
    'transition',
    'OTransition',
    'MozTransition',
    'msTransition'
  ])
  var TRANSITION_END = TRANSITION === 'webkitTransition' || TRANSITION ===
  'OTransition' ? TRANSITION + 'End' : 'transitionend'

  function get (id) {
    return typeof id === 'string'
      ? document.getElementById(id)
      : id
  }

  function getStyle (el, style) {
    var value = el.style[style] || (el.currentStyle && el.currentStyle[style])
    if ((!value || value === 'auto') && document.defaultView) {
      var css = document.defaultView.getComputedStyle(el, null)
      value = css ? css[style] : null
    }
    return value === 'auto' ? null : value
  }

  function create$1 (
    tagName, className, container) {
    var el = document.createElement(tagName)
    el.className = className || ''
    if (container) {container.appendChild(el)}
    return el
  }

  function remove (el) {
    var parent = el.parentNode
    if (parent) {parent.removeChild(el)}
  }

  function empty (el) {while (el.firstChild) {el.removeChild(el.firstChild)}}

  function toFront (el) {
    var parent = el.parentNode
    if (parent && parent.lastChild !== el) {parent.appendChild(el)}
  }

  function toBack (el) {
    var parent = el.parentNode
    if (parent && parent.firstChild !== el) {
      parent.insertBefore(el, parent.firstChild)
    }
  }

  function hasClass (el, name) {
    if (el.classList !== undefined) {return el.classList.contains(name)}
    var className = getClass(el)
    return className.length > 0 &&
      new RegExp('(^|\\s)' + name + '(\\s|$)').test(className)
  }

  function addClass (el, name) {
    if (el.classList !== undefined) {
      var classes = splitWords(name)
      for (var i = 0, len = classes.length; i < len; i++) {
        el.classList.add(classes[i])
      }
    } else if (!hasClass(el, name)) {
      var className = getClass(el)
      setClass(el, (className ? className + ' ' : '') + name)
    }
  }

  function removeClass (el, name) {
    if (el.classList !== undefined) {el.classList.remove(name)} else {
      setClass(el,
        trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')))
    }
  }

  function setClass (el, name) {
    if (el.className.baseVal ===
      undefined) {el.className = name} else {el.className.baseVal = name}
  }

  function getClass (el) {
    if (el.correspondingElement) {el = el.correspondingElement}
    return el.className.baseVal === undefined
      ? el.className
      : el.className.baseVal
  }

  function setOpacity (el, value) {
    if ('opacity' in el.style) {el.style.opacity = value} else if ('filter' in
      el.style) {_setOpacityIE(el, value)}
  }

  function _setOpacityIE (el, value) {
    var filter = false
    var filterName = 'DXImageTransform.Microsoft.Alpha'
    try {filter = el.filters.item(filterName)} catch (e) {
      if (value === 1) {return}
    }
    value = Math.round(value * 100)
    if (filter) {
      filter.Enabled = (value !== 100)
      filter.Opacity = value
    } else {
      el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'
    }
  }

  function testProp (props) {
    var style = document.documentElement.style
    for (var i = 0; i < props.length; i++) {
      if (props[i] in style) {return props[i]}
    }
    return false
  }

  function setTransform (el, offset, scale) {
    var pos = offset || new Point(0, 0)
    el.style[TRANSFORM] = (ie3d
      ? 'translate(' + pos.x + 'px,' + pos.y + 'px)'
      : 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
      (scale ? ' scale(' + scale + ')' : '')
  }

  function setPosition (el, point) {
    el._winfo_pos = point
    if (any3d) {setTransform(el, point)} else {
      el.style.left = point.x + 'px'
      el.style.top = point.y + 'px'
    }
  }

  function getPosition (el) {return el._winfo_pos || new Point(0, 0)}

  var disableTextSelection
  var enableTextSelection
  var _userSelect
  if ('onselectstart' in document) {
    disableTextSelection = function () {
      on(window, 'selectstart', preventDefault)
    }
    enableTextSelection = function () {
      off(window, 'selectstart', preventDefault)
    }
  } else {
    var userSelectProperty = testProp([
      'userSelect',
      'WebkitUserSelect',
      'OUserSelect',
      'MozUserSelect',
      'msUserSelect'
    ])
    disableTextSelection = function () {
      if (userSelectProperty) {
        var style = document.documentElement.style
        _userSelect = style[userSelectProperty]
        style[userSelectProperty] = 'none'
      }
    }
    enableTextSelection = function () {
      if (userSelectProperty) {
        document.documentElement.style[userSelectProperty] = _userSelect
        _userSelect = undefined
      }
    }
  }

  function disableImageDrag () {on(window, 'dragstart', preventDefault)}

  function enableImageDrag () {off(window, 'dragstart', preventDefault)}

  var _outlineElement
  var _outlineStyle

  function preventOutline (element) {
    while (element.tabIndex === -1) {element = element.parentNode}
    if (!element.style) {return}
    restoreOutline()
    _outlineElement = element
    _outlineStyle = element.style.outline
    element.style.outline = 'none'
    on(window, 'keydown', restoreOutline)
  }

  function restoreOutline () {
    if (!_outlineElement) {return}
    _outlineElement.style.outline = _outlineStyle
    _outlineElement = undefined
    _outlineStyle = undefined
    off(window, 'keydown', restoreOutline)
  }

  function getSizedParentNode (element) {
    do {element = element.parentNode} while ((!element.offsetWidth ||
      !element.offsetHeight) && element !== document.body)
    return element
  }

  function getScale (element) {
    var rect = element.getBoundingClientRect()
    return {
      x: rect.width / element.offsetWidth || 1,
      y: rect.height / element.offsetHeight || 1,
      boundingClientRect: rect
    }
  }

  var DomUtil = (Object.freeze || Object)({
    TRANSFORM: TRANSFORM,
    TRANSITION: TRANSITION,
    TRANSITION_END: TRANSITION_END,
    get: get,
    getStyle: getStyle,
    create: create$1,
    remove: remove,
    empty: empty,
    toFront: toFront,
    toBack: toBack,
    hasClass: hasClass,
    addClass: addClass,
    removeClass: removeClass,
    setClass: setClass,
    getClass: getClass,
    setOpacity: setOpacity,
    testProp: testProp,
    setTransform: setTransform,
    setPosition: setPosition,
    getPosition: getPosition,
    disableTextSelection: disableTextSelection,
    enableTextSelection: enableTextSelection,
    disableImageDrag: disableImageDrag,
    enableImageDrag: enableImageDrag,
    preventOutline: preventOutline,
    restoreOutline: restoreOutline,
    getSizedParentNode: getSizedParentNode,
    getScale: getScale
  })

  function on (obj, types, fn, context) {
    if (typeof types === 'object') {
      for (var type in types) {
        addOne(obj, type, types[type], fn)
      }
    } else {
      types = splitWords(types)
      for (var i = 0, len = types.length; i < len; i++) {
        addOne(obj, types[i], fn, context)
      }
    }
    return this
  }

  var eventsKey = '_winfo_events'

  function off (obj, types, fn, context) {
    if (typeof types === 'object') {
      for (var type in types) {
        removeOne(obj, type, types[type], fn)
      }
    } else if (types) {
      types = splitWords(types)
      for (var i = 0, len = types.length; i < len; i++) {
        removeOne(obj, types[i], fn, context)
      }
    } else {
      for (var j in obj[eventsKey]) {removeOne(obj, j, obj[eventsKey][j])}
      delete obj[eventsKey]
    }
    return this
  }

  function addOne (obj, type, fn, context) {
    var id = type + stamp(fn) + (context ? '_' + stamp(context) : '')
    if (obj[eventsKey] && obj[eventsKey][id]) {return this}
    var handler = function (e) {
      return fn.call(context || obj, e || window.event)
    }
    var originalHandler = handler
    if (pointer && type.indexOf('touch') === 0) {
      addPointerListener(obj, type, handler, id)
    } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
      !(pointer && chrome)) {
      addDoubleTapListener(obj, handler, id)
    } else if ('addEventListener' in obj) {
      if (type === 'mousewheel') {
        obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler,
          passiveEvents ? { passive: false } : false)
      } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
        handler = function (e) {
          e = e || window.event
          if (isExternalTarget(obj, e)) {originalHandler(e)}
        }
        obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout',
          handler, false)
      } else {
        if (type === 'click' && android) {
          handler = function (e) {
            filterClick(e, originalHandler)
          }
        }
        obj.addEventListener(type, handler, false)
      }
    } else if ('attachEvent' in obj) {obj.attachEvent('on' + type, handler)}
    obj[eventsKey] = obj[eventsKey] || {}
    obj[eventsKey][id] = handler
  }

  function removeOne (obj, type, fn, context) {
    var id = type + stamp(fn) + (context ? '_' + stamp(context) : '')
    var handler = obj[eventsKey] && obj[eventsKey][id]
    if (!handler) {return this}
    if (pointer && type.indexOf('touch') === 0) {
      removePointerListener(obj, type, id)
    } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
      !(pointer && chrome)) {
      removeDoubleTapListener(obj, id)
    } else if ('removeEventListener' in obj) {
      if (type === 'mousewheel') {
        obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel',
          handler, passiveEvents ? { passive: false } : false)
      } else {
        obj.removeEventListener(
          type === 'mouseenter' ? 'mouseover' : type === 'mouseleave'
            ? 'mouseout'
            : type, handler, false)
      }
    } else if ('detachEvent' in obj) {obj.detachEvent('on' + type, handler)}
    obj[eventsKey][id] = null
  }

  function stopPropagation (e) {
    if (e.stopPropagation) {e.stopPropagation()} else if (e.originalEvent) {e.originalEvent._stopped = true} else {e.cancelBubble = true}
    skipped(e)
    return this
  }

  function disableScrollPropagation (el) {
    addOne(el, 'mousewheel', stopPropagation)
    return this
  }

  function disableClickPropagation (el) {
    on(el, 'mousedown touchstart dblclick', stopPropagation)
    addOne(el, 'click', fakeStop)
    return this
  }

  function preventDefault (e) {
    if (e.preventDefault) {e.preventDefault()} else {e.returnValue = false}
    return this
  }

  function stop (e) {
    preventDefault(e)
    stopPropagation(e)
    return this
  }

  function getMousePosition (e, container) {
    if (!container) {
      return new Point(e.clientX, e.clientY)
    }
    var scale = getScale(container)
    var offset = scale.boundingClientRect
    return new Point((e.clientX - offset.left) / scale.x - container.clientLeft,
      (e.clientY - offset.top) / scale.y - container.clientTop)
  }

  var wheelPxFactor = (win && chrome) ? 2 * window.devicePixelRatio : gecko
    ? window.devicePixelRatio
    : 1

  function getWheelDelta (e) {
    return (edge) ? e.wheelDeltaY / 2 : (e.deltaY && e.deltaMode === 0)
      ? -e.deltaY / wheelPxFactor
      : (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : (e.deltaY &&
        e.deltaMode === 2)
        ? -e.deltaY * 60
        : (e.deltaX || e.deltaZ) ? 0 : e.wheelDelta ? (e.wheelDeltaY ||
          e.wheelDelta) / 2 : (e.detail && Math.abs(e.detail) < 32765)
          ? -e.detail * 20
          : e.detail ? e.detail / -32765 * 60 : 0
  }

  var skipEvents = {}

  function fakeStop (e) {skipEvents[e.type] = true}

  function skipped (e) {
    var events = skipEvents[e.type]
    skipEvents[e.type] = false
    return events
  }

  function isExternalTarget (el, e) {
    var related = e.relatedTarget
    if (!related) {return true}
    try {
      while (related && (related !== el)) {related = related.parentNode}
    } catch (err) {return false}
    return (related !== el)
  }

  var lastClick

  function filterClick (e, handler) {
    var timeStamp = (e.timeStamp ||
      (e.originalEvent && e.originalEvent.timeStamp))
    var elapsed = lastClick && (timeStamp - lastClick)
    if ((elapsed && elapsed > 100 && elapsed < 500) ||
      (e.target._simulatedClick && !e._simulated)) {
      stop(e)
      return
    }
    lastClick = timeStamp
    handler(e)
  }

  var DomEvent = (Object.freeze || Object)({
    on: on,
    off: off,
    stopPropagation: stopPropagation,
    disableScrollPropagation: disableScrollPropagation,
    disableClickPropagation: disableClickPropagation,
    preventDefault: preventDefault,
    stop: stop,
    getMousePosition: getMousePosition,
    getWheelDelta: getWheelDelta,
    fakeStop: fakeStop,
    skipped: skipped,
    isExternalTarget: isExternalTarget,
    addListener: on,
    removeListener: off
  })
  var PosAnimation = Evented.extend({
    run: function (el, newPos, duration, easeLinearity) {
      this.stop()
      this._el = el
      this._inProgress = true
      this._duration = duration || 0.25
      this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2)
      this._startPos = getPosition(el)
      this._offset = newPos.subtract(this._startPos)
      this._startTime = +new Date()
      this.fire('start')
      this._animate()
    },
    stop: function () {
      if (!this._inProgress) {return}
      this._step(true)
      this._complete()
    },
    _animate: function () {
      this._animId = requestAnimFrame(this._animate, this)
      this._step()
    },
    _step: function (round) {
      var elapsed = (+new Date()) - this._startTime
      var duration = this._duration * 1000
      if (elapsed < duration) {
        this._runFrame(this._easeOut(elapsed / duration), round)
      } else {
        this._runFrame(1)
        this._complete()
      }
    },
    _runFrame: function (progress, round) {
      var pos = this._startPos.add(this._offset.multiplyBy(progress))
      if (round) {pos._round()}
      setPosition(this._el, pos)
      this.fire('step')
    },
    _complete: function () {
      cancelAnimFrame(this._animId)
      this._inProgress = false
      this.fire('end')
    },
    _easeOut: function (t) {return 1 - Math.pow(1 - t, this._easeOutPower)}
  })
  var Map = Evented.extend({
    options: {
      crs: EPSG3857,
      center: undefined,
      zoom: undefined,
      minZoom: undefined,
      maxZoom: undefined,
      layers: [],
      maxBounds: new LatLngBounds(new LatLng(-90, -180), new LatLng(90, 180)),
      renderer: undefined,
      zoomAnimation: true,
      zoomAnimationThreshold: 4,
      fadeAnimation: true,
      markerZoomAnimation: true,
      transform3DLimit: 8388608,
      zoomSnap: 1,
      zoomDelta: 1,
      trackResize: true
    },
    initialize: function (id, options) {
      options = setOptions(this, options)
      this._handlers = []
      this._layers = {}
      this._zoomBoundLayers = {}
      this._sizeChanged = true
      this._initContainer(id)
      this._initLayout()
      this._onResize = bind(this._onResize, this)
      this._initEvents()
      this._initDrawEvents()
      if (options.maxBounds) {this.setMaxBounds(options.maxBounds)}
      if (options.zoom !== undefined) {
        this._zoom = this._limitZoom(options.zoom)
      }
      if (options.center && options.zoom !== undefined) {
        this.setView(toLatLng(options.center), options.zoom, { reset: true })
      }
      this.callInitHooks()
      this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
        this.options.zoomAnimation
      if (this._zoomAnimated) {
        this._createAnimProxy()
        on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this)
      }
      this._addLayers(this.options.layers)
      /** 万佳添加，一个高层级的，用于作用鼠标样式**/
      new CanvasLayer().add(this, {
        zIndex: 180,
        className: 'cursorLayer'
      })
    },
    _initDrawEvents: function () {
      const keys = [
        'draw:created',
        'draw:edited',
        'draw:deleted',
        'draw:drawstart',
        'draw:drawstop',
        'draw:drawvertex',
        'draw:editstart',
        'draw:editmove',
        'draw:editresize',
        'draw:editvertex',
        'draw:editstop',
        'draw:deletestart',
        'draw:deletestop',
        'draw:toolbaropened',
        'draw:toolbarclosed',
        'draw:markercontext'
      ]
      keys.map(d => {
        this.on(d, function (e) {
          var eventKey = d.split(':')[1]
          e.layer && e.layer._events[eventKey] && e.layer.fire(eventKey, e)
        })
      })
    },
    setView: function (center, zoom, options) {
      zoom = zoom === undefined
        ? this._zoom
        : this._limitZoom(zoom)
      center = this._limitCenter(toLatLng(center), zoom,
        this.options.maxBounds)
      options = options || {}
      this._stop()
      if (this._loaded && !options.reset && options !== true) {
        if (options.animate !== undefined) {
          options.zoom = extend({ animate: options.animate }, options.zoom)
          options.pan = extend({
            animate: options.animate,
            duration: options.duration
          }, options.pan)
        }
        var moved = (this._zoom !== zoom)
          ? this._tryAnimatedZoom &&
          this._tryAnimatedZoom(center, zoom, options.zoom)
          : this._tryAnimatedPan(center, options.pan)
        if (moved) {
          clearTimeout(this._sizeTimer)
          return this
        }
      }
      this._resetView(center, zoom)
      return this
    },
    setZoom: function (zoom, options) {
      if (!this._loaded) {
        this._zoom = zoom
        return this
      }
      return this.setView(this.getCenter(), zoom, { zoom: options })
    },
    zoomIn: function (delta, options) {
      delta = delta || (any3d ? this.options.zoomDelta : 1)
      return this.setZoom(this._zoom + delta, options)
    },
    zoomOut: function (delta, options) {
      delta = delta || (any3d ? this.options.zoomDelta : 1)
      return this.setZoom(this._zoom - delta, options)
    },
    setZoomAround: function (latlng, zoom, options) {
      var scale = this.getZoomScale(zoom)
      var viewHalf = this.getSize().divideBy(2)
      var containerPoint = latlng instanceof Point
        ? latlng
        : this.latLngToContainerPoint(latlng)
      var centerOffset = containerPoint.subtract(viewHalf).
      multiplyBy(1 - 1 / scale)
      var newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset))
      return this.setView(newCenter, zoom, { zoom: options })
    },
    _getBoundsCenterZoom: function (bounds, options) {
      options = options || {}
      bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds)
      var paddingTL = toPoint(
        options.paddingTopLeft || options.padding || [0, 0])
      var paddingBR = toPoint(
        options.paddingBottomRight || options.padding || [0, 0])
      var zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR))
      zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom,
        zoom) : zoom
      if (zoom === Infinity) {
        return {
          center: bounds.getCenter(),
          zoom: zoom
        }
      }
      var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2)
      var swPoint = this.project(bounds.getSouthWest(), zoom)
      var nePoint = this.project(bounds.getNorthEast(), zoom)
      var center = this.unproject(
        swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom)
      return {
        center: center,
        zoom: zoom
      }
    },
    fitBounds: function (bounds, options) {
      bounds = toLatLngBounds(bounds)
      if (!bounds.isValid()) {throw new Error('Bounds are not valid.')}
      var target = this._getBoundsCenterZoom(bounds, options)
      return this.setView(target.center, target.zoom, options)
    },
    fitWorld: function (options) {
      return this.fitBounds([[-90, -180], [90, 180]], options)
    },
    panTo: function (center, options) {
      return this.setView(center, this._zoom, { pan: options })
    },
    panBy: function (offset, options) {
      offset = toPoint(offset).round()
      options = options || {}
      if (!offset.x && !offset.y) {return this.fire('moveend')}
      if (options.animate !== true && !this.getSize().contains(offset)) {
        this._resetView(
          this.unproject(this.project(this.getCenter()).add(offset)),
          this.getZoom())
        return this
      }
      if (!this._panAnim) {
        this._panAnim = new PosAnimation()
        this._panAnim.on({
          'step': this._onPanTransitionStep,
          'end': this._onPanTransitionEnd
        }, this)
      }
      if (!options.noMoveStart) {this.fire('movestart')}
      if (options.animate !== false) {
        addClass(this._mapPane, 'winfo-pan-anim')
        var newPos = this._getMapPanePos().subtract(offset).round()
        this._panAnim.run(this._mapPane, newPos, options.duration || 0.25,
          options.easeLinearity)
      } else {
        this._rawPanBy(offset)
        this.fire('move').fire('moveend')
      }
      return this
    },
    flyTo: function (targetCenter, targetZoom, options) {
      options = options || {}
      if (options.animate === false || !any3d) {
        return this.setView(targetCenter, targetZoom, options)
      }
      this._stop()
      var from = this.project(this.getCenter())
      var to = this.project(targetCenter)
      var size = this.getSize()
      var startZoom = this._zoom
      targetCenter = toLatLng(targetCenter)
      targetZoom = targetZoom === undefined ? startZoom : targetZoom
      var w0 = Math.max(size.x, size.y)
      var w1 = w0 * this.getZoomScale(startZoom, targetZoom)
      var u1 = (to.distanceTo(from)) || 1
      var rho = 1.42
      var rho2 = rho * rho

      function r (i) {
        var s1 = i ? -1 : 1
        var s2 = i ? w1 : w0
        var t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1
        var b1 = 2 * s2 * rho2 * u1
        var b = t1 / b1
        var sq = Math.sqrt(b * b + 1) - b
        var log = sq < 0.000000001 ? -18 : Math.log(sq)
        return log
      }

      function sinh (n) {return (Math.exp(n) - Math.exp(-n)) / 2}

      function cosh (n) {return (Math.exp(n) + Math.exp(-n)) / 2}

      function tanh (n) {return sinh(n) / cosh(n)}

      var r0 = r(0)

      function w (s) {return w0 * (cosh(r0) / cosh(r0 + rho * s))}

      function u (s) {
        return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2
      }

      function easeOut (t) {return 1 - Math.pow(1 - t, 1.5)}

      var start = Date.now()
      var S = (r(1) - r0) / rho
      var duration = options.duration ? 1000 * options.duration : 1000 * S *
        0.8

      function frame () {
        var t = (Date.now() - start) / duration
        var s = easeOut(t) * S
        if (t <= 1) {
          this._flyToFrame = requestAnimFrame(frame, this)
          this._move(
            this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)),
              startZoom), this.getScaleZoom(w0 / w(s), startZoom),
            { flyTo: true })
        } else {this._move(targetCenter, targetZoom)._moveEnd(true)}
      }

      this._moveStart(true, options.noMoveStart)
      frame.call(this)
      return this
    },
    flyToBounds: function (bounds, options) {
      var target = this._getBoundsCenterZoom(bounds, options)
      return this.flyTo(target.center, target.zoom, options)
    },
    setMaxBounds: function (bounds) {
      bounds = toLatLngBounds(bounds)
      if (!bounds.isValid()) {
        this.options.maxBounds = null
        return this.off('moveend', this._panInsideMaxBounds)
      } else if (this.options.maxBounds) {
        this.off('moveend', this._panInsideMaxBounds)
      }
      this.options.maxBounds = bounds
      if (this._loaded) {this._panInsideMaxBounds()}
      return this.on('moveend', this._panInsideMaxBounds)
    },
    setMinZoom: function (zoom) {
      var oldZoom = this.options.minZoom
      this.options.minZoom = zoom
      if (this._loaded && oldZoom !== zoom) {
        this.fire('zoomlevelschange')
        if (this.getZoom() < this.options.minZoom) {return this.setZoom(zoom)}
      }
      return this
    },
    setMaxZoom: function (zoom) {
      var oldZoom = this.options.maxZoom
      this.options.maxZoom = zoom
      if (this._loaded && oldZoom !== zoom) {
        this.fire('zoomlevelschange')
        if (this.getZoom() > this.options.maxZoom) {return this.setZoom(zoom)}
      }
      return this
    },
    panInsideBounds: function (bounds, options) {
      this._enforcingBounds = true
      var center = this.getCenter()
      var newCenter = this._limitCenter(center, this._zoom,
        toLatLngBounds(bounds))
      if (!center.equals(newCenter)) {this.panTo(newCenter, options)}
      this._enforcingBounds = false
      return this
    },
    panInside: function (latlng, options) {
      options = options || {}
      var paddingTL = toPoint(
        options.paddingTopLeft || options.padding || [0, 0])
      var paddingBR = toPoint(
        options.paddingBottomRight || options.padding || [0, 0])
      var center = this.getCenter()
      var pixelCenter = this.project(center)
      var pixelPoint = this.project(latlng)
      var pixelBounds = this.getPixelBounds()
      var halfPixelBounds = pixelBounds.getSize().divideBy(2)
      var paddedBounds = toBounds([
        pixelBounds.min.add(paddingTL),
        pixelBounds.max.subtract(paddingBR)
      ])
      if (!paddedBounds.contains(pixelPoint)) {
        this._enforcingBounds = true
        var diff = pixelCenter.subtract(pixelPoint)
        var newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y)
        if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x >
          paddedBounds.max.x) {
          newCenter.x = pixelCenter.x - diff.x
          if (diff.x > 0) {
            newCenter.x += halfPixelBounds.x - paddingTL.x
          } else {newCenter.x -= halfPixelBounds.x - paddingBR.x}
        }
        if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y >
          paddedBounds.max.y) {
          newCenter.y = pixelCenter.y - diff.y
          if (diff.y > 0) {
            newCenter.y += halfPixelBounds.y - paddingTL.y
          } else {newCenter.y -= halfPixelBounds.y - paddingBR.y}
        }
        this.panTo(this.unproject(newCenter), options)
        this._enforcingBounds = false
      }
      return this
    },
    invalidateSize: function (options) {
      if (!this._loaded) {return this}
      options = extend({
        animate: false,
        pan: true
      }, options === true ? { animate: true } : options)
      var oldSize = this.getSize()
      this._sizeChanged = true
      this._lastCenter = null
      var newSize = this.getSize()
      var oldCenter = oldSize.divideBy(2).round()
      var newCenter = newSize.divideBy(2).round()
      var offset = oldCenter.subtract(newCenter)
      if (!offset.x && !offset.y) {return this}
      if (options.animate && options.pan) {
        this.panBy(offset)
      } else {
        if (options.pan) {this._rawPanBy(offset)}
        this.fire('move')
        if (options.debounceMoveend) {
          clearTimeout(this._sizeTimer)
          this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200)
        } else {this.fire('moveend')}
      }
      return this.fire('resize', {
        oldSize: oldSize,
        newSize: newSize
      })
    },
    stop: function () {
      this.setZoom(this._limitZoom(this._zoom))
      if (!this.options.zoomSnap) {this.fire('viewreset')}
      return this._stop()
    },
    locate: function (options) {
      options = this._locateOptions = extend({
        timeout: 10000,
        watch: false
      }, options)
      if (!('geolocation' in navigator)) {
        this._handleGeolocationError({
          code: 0,
          message: 'Geolocation not supported.'
        })
        return this
      }
      var onResponse = bind(this._handleGeolocationResponse, this)
      var onError = bind(this._handleGeolocationError, this)
      if (options.watch) {
        this._locationWatchId = navigator.geolocation.watchPosition(onResponse,
          onError, options)
      } else {
        navigator.geolocation.getCurrentPosition(onResponse, onError, options)
      }
      return this
    },
    stopLocate: function () {
      if (navigator.geolocation && navigator.geolocation.clearWatch) {
        navigator.geolocation.clearWatch(this._locationWatchId)
      }
      if (this._locateOptions) {this._locateOptions.setView = false}
      return this
    },
    _handleGeolocationError: function (error) {
      var c = error.code
      var message = error.message || (c === 1 ? 'permission denied' : (c === 2
        ? 'position unavailable'
        : 'timeout'))
      if (this._locateOptions.setView && !this._loaded) {this.fitWorld()}
      this.fire('locationerror', {
        code: c,
        message: 'Geolocation error: ' + message + '.'
      })
    },
    _handleGeolocationResponse: function (pos) {
      var lat = pos.coords.latitude
      var lng = pos.coords.longitude
      var latlng = new LatLng(lat, lng)
      var bounds = latlng.toBounds(pos.coords.accuracy * 2)
      var options = this._locateOptions
      if (options.setView) {
        var zoom = this.getBoundsZoom(bounds)
        this.setView(latlng,
          options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom)
      }
      var data = {
        latlng: latlng,
        bounds: bounds,
        timestamp: pos.timestamp
      }
      for (var i in pos.coords) {
        if (typeof pos.coords[i] === 'number') {data[i] = pos.coords[i]}
      }
      this.fire('locationfound', data)
    },
    addHandler: function (name, HandlerClass) {
      if (!HandlerClass) {return this}
      var handler = this[name] = new HandlerClass(this)
      this._handlers.push(handler)
      if (this.options[name]) {handler.enable()}
      return this
    },
    remove: function () {
      this._initEvents(true)
      if (this._containerId !== this._container._winfo_id) {
        throw new Error('Map container is being reused by another instance')
      }
      try {
        delete this._container._winfo_id
        delete this._containerId
      } catch (e) {
        this._container._winfo_id = undefined
        this._containerId = undefined
      }
      if (this._locationWatchId !== undefined) {this.stopLocate()}
      this._stop()
      remove(this._mapPane)
      if (this._clearControlPos) {this._clearControlPos()}
      if (this._resizeRequest) {
        cancelAnimFrame(this._resizeRequest)
        this._resizeRequest = null
      }
      this._clearHandlers()
      if (this._loaded) {this.fire('unload')}
      var i
      for (i in this._layers) {this._layers[i].remove()}
      for (i in this._panes) {remove(this._panes[i])}
      this._layers = []
      this._panes = []
      delete this._mapPane
      delete this._renderer
      return this
    },
    createPane: function (name, container) {
      var className = 'winfo-pane' +
        (name ? ' winfo-' + name.replace('Pane', '') + '-pane' : '')
      var pane = create$1('div', className, container || this._mapPane)
      if (name) {this._panes[name] = pane}
      return pane
    },
    getCenter: function () {
      this._checkIfLoaded()
      if (this._lastCenter && !this._moved()) {return this._lastCenter}
      return this.layerPointToLatLng(this._getCenterLayerPoint())
    },
    getZoom: function () {return this._zoom},
    getBounds: function () {
      var bounds = this.getPixelBounds()
      var sw = this.unproject(bounds.getBottomLeft())
      var ne = this.unproject(bounds.getTopRight())
      return new LatLngBounds(sw, ne)
    },
    getMinZoom: function () {
      return this.options.minZoom === undefined
        ? this._layersMinZoom || 0
        : this.options.minZoom
    },
    getMaxZoom: function () {
      return this.options.maxZoom === undefined
        ? (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom)
        : this.options.maxZoom
    },
    getBoundsZoom: function (bounds, inside, padding) {
      bounds = toLatLngBounds(bounds)
      padding = toPoint(padding || [0, 0])
      var zoom = this.getZoom() || 0
      var min = this.getMinZoom()
      var max = this.getMaxZoom()
      var nw = bounds.getNorthWest()
      var se = bounds.getSouthEast()
      var size = this.getSize().subtract(padding)
      var boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).
      getSize()
      var snap = any3d ? this.options.zoomSnap : 1
      var scalex = size.x / boundsSize.x
      var scaley = size.y / boundsSize.y
      var scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley)
      zoom = this.getScaleZoom(scale, zoom)
      if (snap) {
        zoom = Math.round(zoom / (snap / 100)) * (snap / 100)
        zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(
          zoom / snap) * snap
      }
      return Math.max(min, Math.min(max, zoom))
    },
    getSize: function () {
      if (!this._size || this._sizeChanged) {
        this._size = new Point(this._container.clientWidth || 0,
          this._container.clientHeight || 0)
        this._sizeChanged = false
      }
      return this._size.clone()
    },
    getPixelBounds: function (center, zoom) {
      var topLeftPoint = this._getTopLeftPoint(center, zoom)
      return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()))
    },
    getPixelOrigin: function () {
      this._checkIfLoaded()
      return this._pixelOrigin
    },
    getPixelWorldBounds: function (zoom) {
      return this.options.crs.getProjectedBounds(
        zoom === undefined ? this.getZoom() : zoom)
    },
    getPane: function (pane) {
      return typeof pane === 'string'
        ? this._panes[pane]
        : pane
    },
    getPanes: function () {return this._panes},
    getContainer: function () {return this._container},
    getZoomScale: function (toZoom, fromZoom) {
      var crs = this.options.crs
      fromZoom = fromZoom === undefined ? this._zoom : fromZoom
      return crs.scale(toZoom) / crs.scale(fromZoom)
    },
    getScaleZoom: function (scale, fromZoom) {
      var crs = this.options.crs
      fromZoom = fromZoom === undefined ? this._zoom : fromZoom
      var zoom = crs.zoom(scale * crs.scale(fromZoom))
      return isNaN(zoom) ? Infinity : zoom
    },
    project: function (latlng, zoom) {
      zoom = zoom === undefined
        ? this._zoom
        : zoom
      return this.options.crs.latLngToPoint(toLatLng(latlng), zoom)
    },
    unproject: function (point, zoom) {
      zoom = zoom === undefined
        ? this._zoom
        : zoom
      return this.options.crs.pointToLatLng(toPoint(point), zoom)
    },
    layerPointToLatLng: function (point) {
      var projectedPoint = toPoint(point).
      add(this.getPixelOrigin())
      return this.unproject(projectedPoint)
    },
    latLngToLayerPoint: function (latlng) {
      var projectedPoint = this.project(toLatLng(latlng))._round()
      return projectedPoint._subtract(this.getPixelOrigin())
    },
    wrapLatLng: function (latlng) {
      return this.options.crs.wrapLatLng(toLatLng(latlng))
    },
    wrapLatLngBounds: function (latlng) {
      return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng))
    },
    distance: function (latlng1, latlng2) {
      return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2))
    },
    containerPointToLayerPoint: function (point) {
      return toPoint(point).
      subtract(this._getMapPanePos())
    },
    layerPointToContainerPoint: function (point) {
      return toPoint(point).
      add(this._getMapPanePos())
    },
    containerPointToLatLng: function (point) {
      var layerPoint = this.containerPointToLayerPoint(toPoint(point))
      return this.layerPointToLatLng(layerPoint)
    },
    latLngToContainerPoint: function (latlng) {
      return this.layerPointToContainerPoint(
        this.latLngToLayerPoint(toLatLng(latlng)))
    },
    mouseEventToContainerPoint: function (e) {
      return getMousePosition(e, this._container)
    },
    mouseEventToLayerPoint: function (e) {
      return this.containerPointToLayerPoint(
        this.mouseEventToContainerPoint(e))
    },
    mouseEventToLatLng: function (e) {
      return this.layerPointToLatLng(this.mouseEventToLayerPoint(e))
    },
    _initContainer: function (id) {
      var container = this._container = get(id)
      if (!container) {
        throw new Error('Map container not found.')
      } else if (container._winfo_id) {
        throw new Error('Map container is already initialized.')
      }
      on(container, 'scroll', this._onScroll, this)
      this._containerId = stamp(container)
    },
    _initLayout: function () {
      var container = this._container
      this._fadeAnimated = this.options.fadeAnimation && any3d
      addClass(container, 'winfo-container' + (touch ? ' winfo-touch' : '') +
        (retina ? ' winfo-retina' : '') + (ielt9 ? ' winfo-oldie' : '') +
        (safari ? ' winfo-safari' : '') +
        (this._fadeAnimated ? ' winfo-fade-anim' : ''))
      var position = getStyle(container, 'position')
      if (position !== 'absolute' && position !== 'relative' && position !==
        'fixed') {container.style.position = 'relative'}
      this._initPanes()
      if (this._initControlPos) {this._initControlPos()}
    },
    _initPanes: function () {
      var panes = this._panes = {}
      this._paneRenderers = {}
      this._mapPane = this.createPane('mapPane', this._container)
      setPosition(this._mapPane, new Point(0, 0))
      this.createPane('tilePane')
      this.createPane('shadowPane')
      this.createPane('overlayPane')
      this.createPane('markerPane')
      this.createPane('tooltipPane')
      this.createPane('popupPane')
      if (!this.options.markerZoomAnimation) {
        addClass(panes.markerPane, 'winfo-zoom-hide')
        addClass(panes.shadowPane, 'winfo-zoom-hide')
      }
    },
    _resetView: function (center, zoom) {
      setPosition(this._mapPane, new Point(0, 0))
      var loading = !this._loaded
      this._loaded = true
      zoom = this._limitZoom(zoom)
      this.fire('viewprereset')
      var zoomChanged = this._zoom !== zoom
      this._moveStart(zoomChanged, false).
      _move(center, zoom).
      _moveEnd(zoomChanged)
      this.fire('viewreset')
      if (loading) {this.fire('load')}
    },
    _moveStart: function (zoomChanged, noMoveStart) {
      if (zoomChanged) {
        this.fire('zoomstart')
      }
      if (!noMoveStart) {this.fire('movestart')}
      return this
    },
    _move: function (center, zoom, data) {
      if (zoom === undefined) {zoom = this._zoom}
      var zoomChanged = this._zoom !== zoom
      this._zoom = zoom
      this._lastCenter = center
      this._pixelOrigin = this._getNewPixelOrigin(center)
      if (zoomChanged || (data && data.pinch)) {this.fire('zoom', data)}
      return this.fire('move', data)
    },
    _moveEnd: function (zoomChanged) {
      if (zoomChanged) {this.fire('zoomend')}
      return this.fire('moveend')
    },
    _stop: function () {
      cancelAnimFrame(this._flyToFrame)
      if (this._panAnim) {this._panAnim.stop()}
      return this
    },
    _rawPanBy: function (offset) {
      setPosition(this._mapPane, this._getMapPanePos().subtract(offset))
    },
    _getZoomSpan: function () {return this.getMaxZoom() - this.getMinZoom()},
    _panInsideMaxBounds: function () {
      if (!this._enforcingBounds) {
        this.panInsideBounds(this.options.maxBounds)
      }
    },
    _checkIfLoaded: function () {
      if (!this._loaded) {
        throw new Error('Set map center and zoom first.')
      }
    },
    _initEvents: function (remove$$1) {
      this._targets = {}
      this._targets[stamp(this._container)] = this
      var onOff = remove$$1 ? off : on
      onOff(this._container, 'click dblclick mousedown mouseup ' +
        'mouseover mouseout mousemove contextmenu keypress keydown keyup',
        this._handleDOMEvent, this)
      if (this.options.trackResize) {
        onOff(window, 'resize', this._onResize, this)
      }
      if (any3d && this.options.transform3DLimit) {
        (remove$$1
          ? this.off
          : this.on).call(this, 'moveend', this._onMoveEnd)
      }
    },
    _onResize: function () {
      cancelAnimFrame(this._resizeRequest)
      this._resizeRequest = requestAnimFrame(
        function () {this.invalidateSize({ debounceMoveend: true })}, this)
    },
    _onScroll: function () {
      this._container.scrollTop = 0
      this._container.scrollLeft = 0
    },
    _onMoveEnd: function () {
      var pos = this._getMapPanePos()
      if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >=
        this.options.transform3DLimit) {
        this._resetView(this.getCenter(), this.getZoom())
      }
    },
    _findEventTargets: function (e, type) {
      var targets = []
      var target
      var isHover = type === 'mouseout' || type === 'mouseover'
      var src = e.target || e.srcElement
      var dragging = false
      while (src) {
        target = this._targets[stamp(src)]
        if (target && (type === 'click' || type === 'preclick') &&
          !e._simulated && this._draggableMoved(target)) {
          dragging = true
          break
        }
        if (target && target.listens(type, true)) {
          if (isHover && !isExternalTarget(src, e)) {break}
          targets.push(target)
          if (isHover) {break}
        }
        if (src === this._container) {break}
        src = src.parentNode
      }
      if (!targets.length && !dragging && !isHover &&
        isExternalTarget(src, e)) {targets = [this]}
      return targets
    },
    _handleDOMEvent: function (e) {
      if (!this._loaded || skipped(e)) {return}
      var type = e.type
      if (type === 'mousedown' || type === 'keypress' || type === 'keyup' ||
        type === 'keydown') {preventOutline(e.target || e.srcElement)}
      this._fireDOMEvent(e, type)
    },
    _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
    _fireDOMEvent: function (e, type, targets) {
      if (e.type === 'click') {
        var synth = extend({}, e)
        synth.type = 'preclick'
        this._fireDOMEvent(synth, synth.type, targets)
      }
      if (e._stopped) {return}
      targets = (targets || []).concat(this._findEventTargets(e, type))
      if (!targets.length) {return}
      var target = targets[0]
      if (type === 'contextmenu' && target.listens(type, true)) {
        preventDefault(e)
      }
      var data = { originalEvent: e }
      if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
        var isMarker = target.getLatLng &&
          (!target._radius || target._radius <= 10)
        data.containerPoint = isMarker ? this.latLngToContainerPoint(
          target.getLatLng()) : this.mouseEventToContainerPoint(e)
        data.layerPoint = this.containerPointToLayerPoint(data.containerPoint)
        data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(
          data.layerPoint)
      }
      for (var i = 0; i < targets.length; i++) {
        targets[i].fire(type, data, true)
        if (data.originalEvent._stopped ||
          (targets[i].options.bubblingMouseEvents === false &&
            indexOf(this._mouseEvents, type) !== -1)) {return}
      }
    },
    _draggableMoved: function (obj) {
      obj = obj.dragging && obj.dragging.enabled()
        ? obj
        : this
      return (obj.dragging && obj.dragging.moved()) ||
        (this.boxZoom && this.boxZoom.moved())
    },
    _clearHandlers: function () {
      for (var i = 0, len = this._handlers.length; i <
      len; i++) {this._handlers[i].disable()}
    },
    whenReady: function (callback, context) {
      if (this._loaded) {
        callback.call(context || this, { target: this })
      } else {this.on('load', callback, context)}
      return this
    },
    _getMapPanePos: function () {
      return getPosition(this._mapPane) || new Point(0, 0)
    },
    _moved: function () {
      var pos = this._getMapPanePos()
      return pos && !pos.equals([0, 0])
    },
    _getTopLeftPoint: function (center, zoom) {
      var pixelOrigin = center && zoom !== undefined ? this._getNewPixelOrigin(
        center, zoom) : this.getPixelOrigin()
      return pixelOrigin.subtract(this._getMapPanePos())
    },
    _getNewPixelOrigin: function (center, zoom) {
      var viewHalf = this.getSize().
      _divideBy(2)
      return this.project(center, zoom).
      _subtract(viewHalf).
      _add(this._getMapPanePos()).
      _round()
    },
    _latLngToNewLayerPoint: function (latlng, zoom, center) {
      var topLeft = this._getNewPixelOrigin(center, zoom)
      return this.project(latlng, zoom)._subtract(topLeft)
    },
    _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
      var topLeft = this._getNewPixelOrigin(center, zoom)
      return toBounds([
        this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
        this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
        this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
        this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
      ])
    },
    _getCenterLayerPoint: function () {
      return this.containerPointToLayerPoint(this.getSize()._divideBy(2))
    },
    _getCenterOffset: function (latlng) {
      return this.latLngToLayerPoint(latlng).
      subtract(this._getCenterLayerPoint())
    },
    _limitCenter: function (center, zoom, bounds) {
      if (!bounds) {return center}
      var centerPoint = this.project(center, zoom)
      var viewHalf = this.getSize().divideBy(2)
      var viewBounds = new Bounds(centerPoint.subtract(viewHalf),
        centerPoint.add(viewHalf))
      var offset = this._getBoundsOffset(viewBounds, bounds, zoom)
      if (offset.round().equals([0, 0])) {return center}
      return this.unproject(centerPoint.add(offset), zoom)
    },
    _limitOffset: function (offset, bounds) {
      if (!bounds) {return offset}
      var viewBounds = this.getPixelBounds()
      var newBounds = new Bounds(viewBounds.min.add(offset),
        viewBounds.max.add(offset))
      return offset.add(this._getBoundsOffset(newBounds, bounds))
    },
    _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
      var projectedMaxBounds = toBounds(
        this.project(maxBounds.getNorthEast(), zoom),
        this.project(maxBounds.getSouthWest(), zoom))
      var minOffset = projectedMaxBounds.min.subtract(pxBounds.min)
      var maxOffset = projectedMaxBounds.max.subtract(pxBounds.max)
      var dx = this._rebound(minOffset.x, -maxOffset.x)
      var dy = this._rebound(minOffset.y, -maxOffset.y)
      return new Point(dx, dy)
    },
    _rebound: function (left, right) {
      return left + right > 0 ? Math.round(left - right) / 2 : Math.max(0,
        Math.ceil(left)) - Math.max(0, Math.floor(right))
    },
    _limitZoom: function (zoom) {
      var min = this.getMinZoom()
      var max = this.getMaxZoom()
      var snap = any3d ? this.options.zoomSnap : 1
      if (snap) {zoom = Math.round(zoom / snap) * snap}
      return Math.max(min, Math.min(max, zoom))
    },
    _onPanTransitionStep: function () {this.fire('move')},
    _onPanTransitionEnd: function () {
      removeClass(this._mapPane, 'winfo-pan-anim')
      this.fire('moveend')
    },
    _tryAnimatedPan: function (center, options) {
      var offset = this._getCenterOffset(center)._trunc()
      if ((options && options.animate) !== true &&
        !this.getSize().contains(offset)) {return false}
      this.panBy(offset, options)
      return true
    },
    _createAnimProxy: function () {
      var proxy = this._proxy = create$1('div',
        'winfo-proxy winfo-zoom-animated')
      this._panes.mapPane.appendChild(proxy)
      this.on('zoomanim', function (e) {
        var prop = TRANSFORM
        var transform = this._proxy.style[prop]
        setTransform(this._proxy, this.project(e.center, e.zoom),
          this.getZoomScale(e.zoom, 1))
        if (transform === this._proxy.style[prop] &&
          this._animatingZoom) {this._onZoomTransitionEnd()}
      }, this)
      this.on('load moveend', this._animMoveEnd, this)
      this._on('unload', this._destroyAnimProxy, this)
    },
    _destroyAnimProxy: function () {
      remove(this._proxy)
      this.off('load moveend', this._animMoveEnd, this)
      delete this._proxy
    },
    _animMoveEnd: function () {
      var c = this.getCenter()
      var z = this.getZoom()
      setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1))
    },
    _catchTransitionEnd: function (e) {
      if (this._animatingZoom && e.propertyName.indexOf('transform') >=
        0) {this._onZoomTransitionEnd()}
    },
    _nothingToAnimate: function () {
      return !this._container.getElementsByClassName(
        'winfo-zoom-animated').length
    },
    _tryAnimatedZoom: function (center, zoom, options) {
      if (this._animatingZoom) {return true}
      options = options || {}
      if (!this._zoomAnimated || options.animate === false ||
        this._nothingToAnimate() || Math.abs(zoom - this._zoom) >
        this.options.zoomAnimationThreshold) {return false}
      var scale = this.getZoomScale(zoom)
      var offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale)
      if (options.animate !== true &&
        !this.getSize().contains(offset)) {return false}
      requestAnimFrame(function () {
        this._moveStart(true, false).
        _animateZoom(center, zoom, true)
      }, this)
      return true
    },
    _animateZoom: function (center, zoom, startAnim, noUpdate) {
      if (!this._mapPane) {return}
      if (startAnim) {
        this._animatingZoom = true
        this._animateToCenter = center
        this._animateToZoom = zoom
        addClass(this._mapPane, 'winfo-zoom-anim')
      }
      this.fire('zoomanim', {
        center: center,
        zoom: zoom,
        noUpdate: noUpdate
      })
      setTimeout(bind(this._onZoomTransitionEnd, this), 250)
    },
    _onZoomTransitionEnd: function () {
      if (!this._animatingZoom) {return}
      if (this._mapPane) {removeClass(this._mapPane, 'winfo-zoom-anim')}
      this._animatingZoom = false
      this._move(this._animateToCenter, this._animateToZoom)
      requestAnimFrame(function () {this._moveEnd(true)}, this)
    }
  })

  function createMap (id, options) {return new Map(id, options)}

  var Control = Class.extend({
    options: { position: 'topright' },
    initialize: function (options) {setOptions(this, options)},
    getPosition: function () {return this.options.position},
    setPosition: function (position) {
      var map = this._map
      if (map) {map.removeControl(this)}
      this.options.position = position
      if (map) {map.addControl(this)}
      return this
    },
    getContainer: function () {return this._container},
    addTo: function (map) {
      this.remove()
      this._map = map
      var container = this._container = this.onAdd(map)
      var pos = this.getPosition()
      var corner = map._controlCorners[pos]
      addClass(container, 'winfo-control')
      if (pos.indexOf('bottom') !== -1) {
        corner.insertBefore(container, corner.firstChild)
      } else {corner.appendChild(container)}
      this._map.on('unload', this.remove, this)
      return this
    },
    remove: function () {
      if (!this._map) {return this}
      remove(this._container)
      if (this.onRemove) {this.onRemove(this._map)}
      this._map.off('unload', this.remove, this)
      this._map = null
      return this
    },
    _refocusOnMap: function (e) {
      if (this._map && e && e.screenX > 0 && e.screenY >
        0) {this._map.getContainer().focus()}
    }
  })
  var control = function (options) {return new Control(options)}
  Map.include({
    addControl: function (control) {
      control.addTo(this)
      return this
    },
    removeControl: function (control) {
      control.remove()
      return this
    },
    _initControlPos: function () {
      var corners = this._controlCorners = {}
      var l = 'winfo-'
      var container = this._controlContainer = create$1('div',
        l + 'control-container', this._container)

      function createCorner (vSide, hSide) {
        var className = l + vSide + ' ' + l + hSide
        corners[vSide + hSide] = create$1('div', className, container)
      }

      createCorner('top', 'left')
      createCorner('top', 'right')
      createCorner('bottom', 'left')
      createCorner('bottom', 'right')
    },
    _clearControlPos: function () {
      for (var i in this._controlCorners) {
        remove(this._controlCorners[i])
      }
      remove(this._controlContainer)
      delete this._controlCorners
      delete this._controlContainer
    }
  })
  var Layers = Control.extend({
    options: {
      collapsed: true,
      position: 'topright',
      autoZIndex: true,
      sortLayers: false,
      hideSingleBase: false,
      sortFunction: function (layerA, layerB, nameA, nameB) {
        return nameA < nameB
          ? -1
          : (nameB < nameA ? 1 : 0)
      }
    },
    initialize: function (baseLayers, overlays, options) {
      setOptions(this, options)
      this._layerControlInputs = []
      this._layers = []
      this._lastZIndex = 0
      this._handlingClick = false
      for (var i in baseLayers) {this._addLayer(baseLayers[i], i)}
      for (i in overlays) {this._addLayer(overlays[i], i, true)}
    },
    onAdd: function (map) {
      this._initLayout()
      this._update()
      this._map = map
      map.on('zoomend', this._checkDisabledLayers, this)
      for (var i = 0; i < this._layers.length; i++) {
        this._layers[i].layer.on('add remove', this._onLayerChange, this)
      }
      return this._container
    },
    addTo: function (map) {
      Control.prototype.addTo.call(this, map)
      return this._expandIfNotCollapsed()
    },
    onRemove: function () {
      this._map.off('zoomend', this._checkDisabledLayers, this)
      for (var i = 0; i < this._layers.length; i++) {
        this._layers[i].layer.off('add remove', this._onLayerChange, this)
      }
    },
    addBaseLayer: function (layer, name) {
      this._addLayer(layer, name)
      return (this._map) ? this._update() : this
    },
    addOverlay: function (layer, name) {
      this._addLayer(layer, name, true)
      return (this._map) ? this._update() : this
    },
    removeLayer: function (layer) {
      layer.off('add remove', this._onLayerChange, this)
      var obj = this._getLayer(stamp(layer))
      if (obj) {this._layers.splice(this._layers.indexOf(obj), 1)}
      return (this._map) ? this._update() : this
    },
    expand: function () {
      addClass(this._container, 'winfo-control-layers-expanded')
      this._section.style.height = null
      var acceptableHeight = this._map.getSize().y -
        (this._container.offsetTop + 50)
      if (acceptableHeight < this._section.clientHeight) {
        addClass(this._section, 'winfo-control-layers-scrollbar')
        this._section.style.height = acceptableHeight + 'px'
      } else {removeClass(this._section, 'winfo-control-layers-scrollbar')}
      this._checkDisabledLayers()
      return this
    },
    collapse: function () {
      removeClass(this._container, 'winfo-control-layers-expanded')
      return this
    },
    _initLayout: function () {
      var className = 'winfo-control-layers'
      var container = this._container = create$1('div', className)
      var collapsed = this.options.collapsed
      container.setAttribute('aria-haspopup', true)
      disableClickPropagation(container)
      disableScrollPropagation(container)
      var section = this._section = create$1('section', className + '-list')
      if (collapsed) {
        this._map.on('click', this.collapse, this)
        if (!android) {
          on(container, {
            mouseenter: this.expand,
            mouseleave: this.collapse
          }, this)
        }
      }
      var link = this._layersLink = create$1('a', className + '-toggle',
        container)
      link.href = '#'
      link.title = 'Layers'
      if (touch) {
        on(link, 'click', stop)
        on(link, 'click', this.expand, this)
      } else {on(link, 'focus', this.expand, this)}
      if (!collapsed) {this.expand()}
      this._baseLayersList = create$1('div', className + '-base', section)
      this._separator = create$1('div', className + '-separator', section)
      this._overlaysList = create$1('div', className + '-overlays', section)
      container.appendChild(section)
    },
    _getLayer: function (id) {
      for (var i = 0; i < this._layers.length; i++) {
        if (this._layers[i] && stamp(this._layers[i].layer) ===
          id) {return this._layers[i]}
      }
    },
    _addLayer: function (layer, name, overlay) {
      if (this._map) {
        layer.on('add remove', this._onLayerChange, this)
      }
      this._layers.push({
        layer: layer,
        name: name,
        overlay: overlay
      })
      if (this.options.sortLayers) {
        this._layers.sort(bind(function (a, b) {
          return this.options.sortFunction(a.layer, b.layer, a.name, b.name)
        }, this))
      }
      if (this.options.autoZIndex && layer.setZIndex) {
        this._lastZIndex++
        layer.setZIndex(this._lastZIndex)
      }
      this._expandIfNotCollapsed()
    },
    _update: function () {
      if (!this._container) {return this}
      empty(this._baseLayersList)
      empty(this._overlaysList)
      this._layerControlInputs = []
      var baseLayersPresent
      var overlaysPresent
      var i
      var obj
      var baseLayersCount = 0
      for (i = 0; i < this._layers.length; i++) {
        obj = this._layers[i]
        this._addItem(obj)
        overlaysPresent = overlaysPresent || obj.overlay
        baseLayersPresent = baseLayersPresent || !obj.overlay
        baseLayersCount += !obj.overlay ? 1 : 0
      }
      if (this.options.hideSingleBase) {
        baseLayersPresent = baseLayersPresent && baseLayersCount > 1
        this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'
      }
      this._separator.style.display = overlaysPresent && baseLayersPresent
        ? ''
        : 'none'
      return this
    },
    _onLayerChange: function (e) {
      if (!this._handlingClick) {this._update()}
      var obj = this._getLayer(stamp(e.target))
      var type = obj.overlay ? (e.type === 'add'
        ? 'overlayadd'
        : 'overlayremove') : (e.type === 'add' ? 'baselayerchange' : null)
      if (type) {this._map.fire(type, obj)}
    },
    _createRadioElement: function (name, checked) {
      var radioHtml = '<input type="radio" class="winfo-control-layers-selector" name="' +
        name + '"' + (checked ? ' checked="checked"' : '') + '/>'
      var radioFragment = document.createElement('div')
      radioFragment.innerHTML = radioHtml
      return radioFragment.firstChild
    },
    _addItem: function (obj) {
      var label = document.createElement('label')
      var checked = this._map.hasLayer(obj.layer)
      var input
      if (obj.overlay) {
        input = document.createElement('input')
        input.type = 'checkbox'
        input.className = 'winfo-control-layers-selector'
        input.defaultChecked = checked
      } else {
        input = this._createRadioElement('winfo-base-layers_' + stamp(this),
          checked)
      }
      this._layerControlInputs.push(input)
      input.layerId = stamp(obj.layer)
      on(input, 'click', this._onInputClick, this)
      var name = document.createElement('span')
      name.innerHTML = ' ' + obj.name
      var holder = document.createElement('div')
      label.appendChild(holder)
      holder.appendChild(input)
      holder.appendChild(name)
      var container = obj.overlay ? this._overlaysList : this._baseLayersList
      container.appendChild(label)
      this._checkDisabledLayers()
      return label
    },
    _onInputClick: function () {
      var inputs = this._layerControlInputs
      var input
      var layer
      var addedLayers = []
      var removedLayers = []
      this._handlingClick = true
      for (var i = inputs.length - 1; i >= 0; i--) {
        input = inputs[i]
        layer = this._getLayer(input.layerId).layer
        if (input.checked) {
          addedLayers.push(layer)
        } else if (!input.checked) {removedLayers.push(layer)}
      }
      for (i = 0; i < removedLayers.length; i++) {
        if (this._map.hasLayer(removedLayers[i])) {
          this._map.removeLayer(removedLayers[i])
        }
      }
      for (i = 0; i < addedLayers.length; i++) {
        if (!this._map.hasLayer(addedLayers[i])) {
          this._map.addLayer(addedLayers[i])
        }
      }
      this._handlingClick = false
      this._refocusOnMap()
    },
    _checkDisabledLayers: function () {
      var inputs = this._layerControlInputs
      var input
      var layer
      var zoom = this._map.getZoom()
      for (var i = inputs.length - 1; i >= 0; i--) {
        input = inputs[i]
        layer = this._getLayer(input.layerId).layer
        input.disabled = (layer.options.minZoom !== undefined && zoom <
          layer.options.minZoom) ||
          (layer.options.maxZoom !== undefined && zoom >
            layer.options.maxZoom)
      }
    },
    _expandIfNotCollapsed: function () {
      if (this._map && !this.options.collapsed) {this.expand()}
      return this
    },
    _expand: function () {return this.expand()},
    _collapse: function () {return this.collapse()}
  })
  var layers = function (baseLayers, overlays, options) {
    return new Layers(baseLayers, overlays, options)
  }
  var Zoom = Control.extend({
    options: {
      position: 'topleft',
      zoomInText: '+',
      zoomInTitle: 'Zoom in',
      zoomOutText: '&#x2212;',
      zoomOutTitle: 'Zoom out'
    },
    onAdd: function (map) {
      var zoomName = 'winfo-control-zoom'
      var container = create$1('div', zoomName + ' winfo-bar')
      var options = this.options
      this._zoomInButton = this._createButton(options.zoomInText,
        options.zoomInTitle, zoomName + '-in', container, this._zoomIn)
      this._zoomOutButton = this._createButton(options.zoomOutText,
        options.zoomOutTitle, zoomName + '-out', container, this._zoomOut)
      this._updateDisabled()
      map.on('zoomend zoomlevelschange', this._updateDisabled, this)
      return container
    },
    onRemove: function (map) {
      map.off('zoomend zoomlevelschange', this._updateDisabled, this)
    },
    disable: function () {
      this._disabled = true
      this._updateDisabled()
      return this
    },
    enable: function () {
      this._disabled = false
      this._updateDisabled()
      return this
    },
    _zoomIn: function (e) {
      if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
        this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1))
      }
    },
    _zoomOut: function (e) {
      if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
        this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1))
      }
    },
    _createButton: function (html, title, className, container, fn) {
      var link = create$1('a', className, container)
      link.innerHTML = html
      link.href = '#'
      link.title = title
      link.setAttribute('role', 'button')
      link.setAttribute('aria-label', title)
      disableClickPropagation(link)
      on(link, 'click', stop)
      on(link, 'click', fn, this)
      on(link, 'click', this._refocusOnMap, this)
      return link
    },
    _updateDisabled: function () {
      var map = this._map
      var className = 'winfo-disabled'
      removeClass(this._zoomInButton, className)
      removeClass(this._zoomOutButton, className)
      if (this._disabled || map._zoom === map.getMinZoom()) {
        addClass(this._zoomOutButton, className)
      }
      if (this._disabled || map._zoom === map.getMaxZoom()) {
        addClass(this._zoomInButton, className)
      }
    }
  })
  Map.mergeOptions({ zoomControl: true })
  Map.addInitHook(function () {
    if (this.options.zoomControl) {
      this.zoomControl = new Zoom()
      this.addControl(this.zoomControl)
    }
  })
  var zoom = function (options) {return new Zoom(options)}
  var Scale = Control.extend({
    options: {
      position: 'bottomleft',
      maxWidth: 100,
      metric: true,
      imperial: true
    },
    onAdd: function (map) {
      var className = 'winfo-control-scale'
      var container = create$1('div', className)
      var options = this.options
      this._addScales(options, className + '-line', container)
      map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this)
      map.whenReady(this._update, this)
      return container
    },
    onRemove: function (map) {
      map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update,
        this)
    },
    _addScales: function (options, className, container) {
      if (options.metric) {
        this._mScale = create$1('div', className, container)
      }
      if (options.imperial) {
        this._iScale = create$1('div', className, container)
      }
    },
    _update: function () {
      var map = this._map
      var y = map.getSize().y / 2
      var maxMeters = map.distance(map.containerPointToLatLng([0, y]),
        map.containerPointToLatLng([this.options.maxWidth, y]))
      this._updateScales(maxMeters)
    },
    _updateScales: function (maxMeters) {
      if (this.options.metric && maxMeters) {this._updateMetric(maxMeters)}
      if (this.options.imperial && maxMeters) {this._updateImperial(maxMeters)}
    },
    _updateMetric: function (maxMeters) {
      var meters = this._getRoundNum(maxMeters)
      var label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'
      this._updateScale(this._mScale, label, meters / maxMeters)
    },
    _updateImperial: function (maxMeters) {
      var maxFeet = maxMeters * 3.2808399
      var maxMiles
      var miles
      var feet
      if (maxFeet > 5280) {
        maxMiles = maxFeet / 5280
        miles = this._getRoundNum(maxMiles)
        this._updateScale(this._iScale, miles + ' mi', miles / maxMiles)
      } else {
        feet = this._getRoundNum(maxFeet)
        this._updateScale(this._iScale, feet + ' ft', feet / maxFeet)
      }
    },
    _updateScale: function (scale, text, ratio) {
      scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'
      scale.innerHTML = text
    },
    _getRoundNum: function (num) {
      var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1)
      var d = num / pow10
      d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1
      return pow10 * d
    }
  })
  var scale = function (options) {return new Scale(options)}
  var Attribution = Control.extend({
    options: {
      position: 'bottomright',
      prefix: '<a href="http://www.winfo.cc/">闻迅科技</a>'
    },
    initialize: function (options) {
      setOptions(this, options)
      this._attributions = {}
    },
    onAdd: function (map) {
      map.attributionControl = this
      this._container = create$1('div', 'winfo-control-attribution')
      disableClickPropagation(this._container)
      for (var i in map._layers) {
        if (map._layers[i].getAttribution) {
          this.addAttribution(map._layers[i].getAttribution())
        }
      }
      this._update()
      return this._container
    },
    setPrefix: function (prefix) {
      this.options.prefix = prefix
      this._update()
      return this
    },
    addAttribution: function (text) {
      if (!text) {return this}
      if (!this._attributions[text]) {this._attributions[text] = 0}
      this._attributions[text]++
      this._update()
      return this
    },
    removeAttribution: function (text) {
      if (!text) {return this}
      if (this._attributions[text]) {
        this._attributions[text]--
        this._update()
      }
      return this
    },
    _update: function () {
      if (!this._map) {return}
      var attribs = []
      for (var i in this._attributions) {
        if (this._attributions[i]) {
          attribs.push(i)
        }
      }
      var prefixAndAttribs = []
      if (this.options.prefix) {prefixAndAttribs.push(this.options.prefix)}
      if (attribs.length) {prefixAndAttribs.push(attribs.join(', '))}
      this._container.innerHTML = prefixAndAttribs.join(' | ')
    }
  })
  Map.mergeOptions({ attributionControl: true })
  Map.addInitHook(function () {
    if (this.options.attributionControl) {
      new Attribution().addTo(this)
    }
  })
  var attribution = function (options) {return new Attribution(options)}
  Control.Layers = Layers
  Control.Zoom = Zoom
  Control.Scale = Scale
  Control.Attribution = Attribution
  control.layers = layers
  control.zoom = zoom
  control.scale = scale
  control.attribution = attribution
  var Handler = Class.extend({
    initialize: function (map) {this._map = map},
    enable: function () {
      if (this._enabled) {return this}
      this._enabled = true
      this.addHooks()
      return this
    },
    disable: function () {
      if (!this._enabled) {return this}
      this._enabled = false
      this.removeHooks()
      return this
    },
    enabled: function () {return !!this._enabled}
  })
  Handler.addTo = function (map, name) {
    map.addHandler(name, this)
    return this
  }
  var Mixin = { Events: Events }
  var START = touch ? 'touchstart mousedown' : 'mousedown'
  var END = {
    mousedown: 'mouseup',
    touchstart: 'touchend',
    pointerdown: 'touchend',
    MSPointerDown: 'touchend'
  }
  var MOVE = {
    mousedown: 'mousemove',
    touchstart: 'touchmove',
    pointerdown: 'touchmove',
    MSPointerDown: 'touchmove'
  }
  var Draggable = Evented.extend({
    options: { clickTolerance: 3 },
    initialize: function (
      element, dragStartTarget, preventOutline$$1, options) {
      setOptions(this, options)
      this._element = element
      this._dragStartTarget = dragStartTarget || element
      this._preventOutline = preventOutline$$1
    },
    enable: function () {
      if (this._enabled) {return}
      on(this._dragStartTarget, START, this._onDown, this)
      this._enabled = true
    },
    disable: function () {
      if (!this._enabled) {return}
      if (Draggable._dragging === this) {this.finishDrag()}
      off(this._dragStartTarget, START, this._onDown, this)
      this._enabled = false
      this._moved = false
    },
    _onDown: function (e) {
      if (e._simulated || !this._enabled) {return}
      this._moved = false
      if (hasClass(this._element, 'winfo-zoom-anim')) {return}
      if (Draggable._dragging || e.shiftKey ||
        ((e.which !== 1) && (e.button !== 1) && !e.touches)) {return}
      Draggable._dragging = this
      if (this._preventOutline) {preventOutline(this._element)}
      disableImageDrag()
      disableTextSelection()
      if (this._moving) {return}
      this.fire('down')
      var first = e.touches ? e.touches[0] : e
      var sizedParent = getSizedParentNode(this._element)
      this._startPoint = new Point(first.clientX, first.clientY)
      this._parentScale = getScale(sizedParent)
      on(document, MOVE[e.type], this._onMove, this)
      on(document, END[e.type], this._onUp, this)
    },
    _onMove: function (e) {
      if (e._simulated || !this._enabled) {return}
      if (e.touches && e.touches.length > 1) {
        this._moved = true
        return
      }
      var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e)
      var offset = new Point(first.clientX, first.clientY)._subtract(
        this._startPoint)
      if (!offset.x && !offset.y) {return}
      if (Math.abs(offset.x) + Math.abs(offset.y) <
        this.options.clickTolerance) {return}
      offset.x /= this._parentScale.x
      offset.y /= this._parentScale.y
      preventDefault(e)
      if (!this._moved) {
        this.fire('dragstart')
        this._moved = true
        this._startPos = getPosition(this._element).subtract(offset)
        addClass(document.body, 'winfo-dragging')
        this._lastTarget = e.target || e.srcElement
        if ((window.SVGElementInstance) && (this._lastTarget instanceof
          SVGElementInstance)) {this._lastTarget = this._lastTarget.correspondingUseElement}
        addClass(this._lastTarget, 'winfo-drag-target')
      }
      this._newPos = this._startPos.add(offset)
      this._moving = true
      cancelAnimFrame(this._animRequest)
      this._lastEvent = e
      this._animRequest = requestAnimFrame(this._updatePosition, this, true)
    },
    _updatePosition: function () {
      var e = { originalEvent: this._lastEvent }
      this.fire('predrag', e)
      setPosition(this._element, this._newPos)
      this.fire('drag', e)
    },
    _onUp: function (e) {
      if (e._simulated || !this._enabled) {return}
      this.finishDrag()
    },
    finishDrag: function () {
      removeClass(document.body, 'winfo-dragging')
      if (this._lastTarget) {
        removeClass(this._lastTarget, 'winfo-drag-target')
        this._lastTarget = null
      }
      for (var i in MOVE) {
        off(document, MOVE[i], this._onMove, this)
        off(document, END[i], this._onUp, this)
      }
      enableImageDrag()
      enableTextSelection()
      if (this._moved && this._moving) {
        cancelAnimFrame(this._animRequest)
        this.fire('dragend',
          { distance: this._newPos.distanceTo(this._startPos) })
      }
      this._moving = false
      Draggable._dragging = false
    }
  })

  function simplify (points, tolerance) {
    if (!tolerance || !points.length) {return points.slice()}
    var sqTolerance = tolerance * tolerance
    points = _reducePoints(points, sqTolerance)
    points = _simplifyDP(points, sqTolerance)
    return points
  }

  function pointToSegmentDistance (p, p1, p2) {
    return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true))
  }

  function closestPointOnSegment (p, p1, p2) {
    return _sqClosestPointOnSegment(p, p1, p2)
  }

  function _simplifyDP (points, sqTolerance) {
    var len = points.length
    var ArrayConstructor = typeof Uint8Array !== undefined + ''
      ? Uint8Array
      : Array
    var markers = new ArrayConstructor(len)
    markers[0] = markers[len - 1] = 1
    _simplifyDPStep(points, markers, sqTolerance, 0, len - 1)
    var i
    var newPoints = []
    for (i = 0; i < len; i++) {if (markers[i]) {newPoints.push(points[i])}}
    return newPoints
  }

  function _simplifyDPStep (
    points, markers, sqTolerance, first, last) {
    var maxSqDist = 0
    var index
    var i
    var sqDist
    for (i = first + 1; i <= last - 1; i++) {
      sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last],
        true)
      if (sqDist > maxSqDist) {
        index = i
        maxSqDist = sqDist
      }
    }
    if (maxSqDist > sqTolerance) {
      markers[index] = 1
      _simplifyDPStep(points, markers, sqTolerance, first, index)
      _simplifyDPStep(points, markers, sqTolerance, index, last)
    }
  }

  function _reducePoints (points, sqTolerance) {
    var reducedPoints = [points[0]]
    for (var i = 1, prev = 0, len = points.length; i < len; i++) {
      if (_sqDist(points[i], points[prev]) > sqTolerance) {
        reducedPoints.push(points[i])
        prev = i
      }
    }
    if (prev < len - 1) {reducedPoints.push(points[len - 1])}
    return reducedPoints
  }

  var _lastCode

  function clipSegment (
    a, b, bounds, useLastCode, round) {
    var codeA = useLastCode
      ? _lastCode
      : _getBitCode(a, bounds)
    var codeB = _getBitCode(b, bounds)
    var codeOut
    var p
    var newCode
    _lastCode = codeB
    while (true) {
      if (!(codeA | codeB)) {return [a, b]}
      if (codeA & codeB) {return false}
      codeOut = codeA || codeB
      p = _getEdgeIntersection(a, b, codeOut, bounds, round)
      newCode = _getBitCode(p, bounds)
      if (codeOut === codeA) {
        a = p
        codeA = newCode
      } else {
        b = p
        codeB = newCode
      }
    }
  }

  function _getEdgeIntersection (a, b, code, bounds, round) {
    var dx = b.x - a.x
    var dy = b.y - a.y
    var min = bounds.min
    var max = bounds.max
    var x
    var y
    if (code & 8) {
      x = a.x + dx * (max.y - a.y) / dy
      y = max.y
    } else if (code & 4) {
      x = a.x + dx * (min.y - a.y) / dy
      y = min.y
    } else if (code & 2) {
      x = max.x
      y = a.y + dy * (max.x - a.x) / dx
    } else if (code & 1) {
      x = min.x
      y = a.y + dy * (min.x - a.x) / dx
    }
    return new Point(x, y, round)
  }

  function _getBitCode (p, bounds) {
    var code = 0
    if (p.x < bounds.min.x) {code |= 1} else if (p.x >
      bounds.max.x) {code |= 2}
    if (p.y < bounds.min.y) {code |= 4} else if (p.y >
      bounds.max.y) {code |= 8}
    return code
  }

  function _sqDist (p1, p2) {
    var dx = p2.x - p1.x
    var dy = p2.y - p1.y
    return dx * dx + dy * dy
  }

  function _sqClosestPointOnSegment (p, p1, p2, sqDist) {
    var x = p1.x
    var y = p1.y
    var dx = p2.x - x
    var dy = p2.y - y
    var dot = dx * dx + dy * dy
    var t
    if (dot > 0) {
      t = ((p.x - x) * dx + (p.y - y) * dy) / dot
      if (t > 1) {
        x = p2.x
        y = p2.y
      } else if (t > 0) {
        x += dx * t
        y += dy * t
      }
    }
    dx = p.x - x
    dy = p.y - y
    return sqDist ? dx * dx + dy * dy : new Point(x, y)
  }

  function isFlat (latlngs) {
    return !isArray(latlngs[0]) ||
      (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !==
        'undefined')
  }

  function _flat (latlngs) {
    console.warn(
      'Deprecated use of _flat, please use L.LineUtil.isFlat instead.')
    return isFlat(latlngs)
  }

  var LineUtil = (Object.freeze || Object)({
    simplify: simplify,
    pointToSegmentDistance: pointToSegmentDistance,
    closestPointOnSegment: closestPointOnSegment,
    clipSegment: clipSegment,
    _getEdgeIntersection: _getEdgeIntersection,
    _getBitCode: _getBitCode,
    _sqClosestPointOnSegment: _sqClosestPointOnSegment,
    isFlat: isFlat,
    _flat: _flat
  })

  function clipPolygon (points, bounds, round) {
    var clippedPoints
    var edges = [1, 4, 2, 8]
    var i
    var j
    var k
    var a
    var b
    var len
    var edge
    var p
    for (i = 0, len = points.length; i <
    len; i++) {points[i]._code = _getBitCode(points[i], bounds)}
    for (k = 0; k < 4; k++) {
      edge = edges[k]
      clippedPoints = []
      for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
        a = points[i]
        b = points[j]
        if (!(a._code & edge)) {
          if (b._code & edge) {
            p = _getEdgeIntersection(b, a, edge, bounds, round)
            p._code = _getBitCode(p, bounds)
            clippedPoints.push(p)
          }
          clippedPoints.push(a)
        } else if (!(b._code & edge)) {
          p = _getEdgeIntersection(b, a, edge, bounds, round)
          p._code = _getBitCode(p, bounds)
          clippedPoints.push(p)
        }
      }
      points = clippedPoints
    }
    return points
  }

  var PolyUtil = (Object.freeze || Object)({ clipPolygon: clipPolygon })
  var LonLat = {
    project: function (latlng) {
      return new Point(latlng.lng, latlng.lat)
    },
    unproject: function (point) {return new LatLng(point.y, point.x)},
    bounds: new Bounds([-180, -90], [180, 90])
  }
  var Mercator = {
    R: 6378137,
    R_MINOR: 6356752.314245179,
    bounds: new Bounds([-20037508.34279, -15496570.73972],
      [20037508.34279, 18764656.23138]),
    project: function (latlng) {
      var d = Math.PI / 180
      var r = this.R
      var y = latlng.lat * d
      var tmp = this.R_MINOR / r
      var e = Math.sqrt(1 - tmp * tmp)
      var con = e * Math.sin(y)
      var ts = Math.tan(Math.PI / 4 - y / 2) /
        Math.pow((1 - con) / (1 + con), e / 2)
      y = -r * Math.log(Math.max(ts, 1E-10))
      return new Point(latlng.lng * d * r, y)
    },
    unproject: function (point) {
      var d = 180 / Math.PI
      var r = this.R
      var tmp = this.R_MINOR / r
      var e = Math.sqrt(1 - tmp * tmp)
      var ts = Math.exp(-point.y / r)
      var phi = Math.PI / 2 - 2 * Math.atan(ts)
      for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
        con = e * Math.sin(phi)
        con = Math.pow((1 - con) / (1 + con), e / 2)
        dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi
        phi += dphi
      }
      return new LatLng(phi * d, point.x * d / r)
    }
  }
  var index = (Object.freeze || Object)({
    LonLat: LonLat,
    Mercator: Mercator,
    SphericalMercator: SphericalMercator
  })
  var EPSG3395 = extend({}, Earth, {
    code: 'EPSG:3395',
    projection: Mercator,
    transformation: (function () {
      var scale = 0.5 / (Math.PI * Mercator.R)
      return toTransformation(scale, 0.5, -scale, 0.5)
    }())
  })
  var EPSG4326 = extend({}, Earth, {
    code: 'EPSG:4326',
    projection: LonLat,
    transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
  })
  var Simple = extend({}, CRS, {
    projection: LonLat,
    transformation: toTransformation(1, 0, -1, 0),
    scale: function (zoom) {return Math.pow(2, zoom)},
    zoom: function (scale) {return Math.log(scale) / Math.LN2},
    distance: function (latlng1, latlng2) {
      var dx = latlng2.lng - latlng1.lng
      var dy = latlng2.lat - latlng1.lat
      return Math.sqrt(dx * dx + dy * dy)
    },
    infinite: true
  })
  CRS.Earth = Earth
  CRS.EPSG3395 = EPSG3395
  CRS.EPSG3857 = EPSG3857
  CRS.EPSG900913 = EPSG900913
  CRS.EPSG4326 = EPSG4326
  CRS.Simple = Simple
  var Layer = Evented.extend({
    options: {
      pane: 'overlayPane',
      attribution: null,
      bubblingMouseEvents: true
    },
    addTo: function (map) {
      map.addLayer(this)
      return this
    },
    remove: function () {return this.removeFrom(this._map || this._mapToAdd)},
    removeFrom: function (obj) {
      if (obj) {obj.removeLayer(this)}
      return this
    },
    getPane: function (name) {
      return this._map.getPane(
        name ? (this.options[name] || name) : this.options.pane)
    },
    addInteractiveTarget: function (targetEl) {
      this._map._targets[stamp(targetEl)] = this
      return this
    },
    removeInteractiveTarget: function (targetEl) {
      delete this._map._targets[stamp(targetEl)]
      return this
    },
    getAttribution: function () {return this.options.attribution},
    _layerAdd: function (e) {
      var map = e.target
      if (!map.hasLayer(this)) {return}
      this._map = map
      this._zoomAnimated = map._zoomAnimated
      if (this.getEvents) {
        var events = this.getEvents()
        map.on(events, this)
        this.once('remove', function () {map.off(events, this)}, this)
      }
      this.onAdd(map)
      if (this.getAttribution && map.attributionControl) {
        map.attributionControl.addAttribution(this.getAttribution())
      }
      this.fire('add')
      map.fire('layeradd', { layer: this })
    }
  })
  Map.include({
    addLayer: function (layer) {
      if (!layer._layerAdd) {
        throw new Error('The provided object is not a Layer.')
      }
      var id = stamp(layer)
      if (this._layers[id]) {return this}
      this._layers[id] = layer
      layer._mapToAdd = this
      if (layer.beforeAdd) {layer.beforeAdd(this)}
      this.whenReady(layer._layerAdd, layer)
      return this
    },
    removeLayer: function (layer) {
      var id = stamp(layer)
      if (!this._layers[id]) {return this}
      if (this._loaded) {layer.onRemove(this)}
      if (layer.getAttribution && this.attributionControl) {
        this.attributionControl.removeAttribution(layer.getAttribution())
      }
      delete this._layers[id]
      if (this._loaded) {
        this.fire('layerremove', { layer: layer })
        layer.fire('remove')
      }
      layer._map = layer._mapToAdd = null
      return this
    },
    hasLayer: function (layer) {
      return !!layer && (stamp(layer) in this._layers)
    },
    eachLayer: function (method, context) {
      for (var i in this._layers) {
        method.call(context, this._layers[i])
      }
      return this
    },
    _addLayers: function (layers) {
      layers = layers ? (isArray(layers)
        ? layers
        : [layers]) : []
      for (var i = 0, len = layers.length; i < len; i++) {
        this.addLayer(layers[i])
      }
    },
    _addZoomLimit: function (layer) {
      if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
        this._zoomBoundLayers[stamp(layer)] = layer
        this._updateZoomLevels()
      }
    },
    _removeZoomLimit: function (layer) {
      var id = stamp(layer)
      if (this._zoomBoundLayers[id]) {
        delete this._zoomBoundLayers[id]
        this._updateZoomLevels()
      }
    },
    _updateZoomLevels: function () {
      var minZoom = Infinity
      var maxZoom = -Infinity
      var oldZoomSpan = this._getZoomSpan()
      for (var i in this._zoomBoundLayers) {
        var options = this._zoomBoundLayers[i].options
        minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom,
          options.minZoom)
        maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom,
          options.maxZoom)
      }
      this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom
      this._layersMinZoom = minZoom === Infinity ? undefined : minZoom
      if (oldZoomSpan !== this._getZoomSpan()) {this.fire('zoomlevelschange')}
      if (this.options.maxZoom === undefined && this._layersMaxZoom &&
        this.getZoom() > this._layersMaxZoom) {
        this.setZoom(this._layersMaxZoom)
      }
      if (this.options.minZoom === undefined && this._layersMinZoom &&
        this.getZoom() < this._layersMinZoom) {
        this.setZoom(this._layersMinZoom)
      }
    }
  })
  var LayerGroup = Layer.extend({
    initialize: function (layers, options) {
      setOptions(this, options)
      this._layers = {}
      var i, len
      if (layers) {
        for (i = 0, len = layers.length; i < len; i++) {
          this.addLayer(layers[i])
        }
      }
    },
    addLayer: function (layer) {
      var id = this.getLayerId(layer)
      this._layers[id] = layer
      if (this._map) {this._map.addLayer(layer)}
      return this
    },
    removeLayer: function (layer) {
      var id = layer in this._layers
        ? layer
        : this.getLayerId(layer)
      if (this._map && this._layers[id]) {
        this._map.removeLayer(this._layers[id])
      }
      delete this._layers[id]
      return this
    },
    hasLayer: function (layer) {
      return !!layer &&
        (layer in this._layers || this.getLayerId(layer) in this._layers)
    },
    clearLayers: function () {return this.eachLayer(this.removeLayer, this)},
    invoke: function (methodName) {
      var args = Array.prototype.slice.call(arguments, 1)
      var i
      var layer
      for (i in this._layers) {
        layer = this._layers[i]
        if (layer[methodName]) {layer[methodName].apply(layer, args)}
      }
      return this
    },
    onAdd: function (map) {this.eachLayer(map.addLayer, map)},
    onRemove: function (map) {this.eachLayer(map.removeLayer, map)},
    eachLayer: function (method, context) {
      for (var i in this._layers) {
        method.call(context, this._layers[i])
      }
      return this
    },
    getLayer: function (id) {return this._layers[id]},
    getLayers: function () {
      var layers = []
      this.eachLayer(layers.push, layers)
      return layers
    },
    setZIndex: function (zIndex) {return this.invoke('setZIndex', zIndex)},
    getLayerId: function (layer) {return stamp(layer)}
  })
  var layerGroup = function (layers, options) {
    return new LayerGroup(layers, options)
  }
  var FeatureGroup = LayerGroup.extend({
    addLayer: function (layer) {
      if (this.hasLayer(layer)) {return this}
      layer.addEventParent(this)
      LayerGroup.prototype.addLayer.call(this, layer)
      return this.fire('layeradd', { layer: layer })
    },
    removeLayer: function (layer) {
      if (!this.hasLayer(layer)) {return this}
      if (layer in this._layers) {layer = this._layers[layer]}
      layer.removeEventParent(this)
      LayerGroup.prototype.removeLayer.call(this, layer)
      return this.fire('layerremove', { layer: layer })
    },
    setStyle: function (style) {return this.invoke('setStyle', style)},
    bringToFront: function () {return this.invoke('bringToFront')},
    bringToBack: function () {return this.invoke('bringToBack')},
    getBounds: function () {
      var bounds = new LatLngBounds()
      for (var id in this._layers) {
        var layer = this._layers[id]
        bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng())
      }
      return bounds
    }
  })
  var featureGroup = function (layers) {return new FeatureGroup(layers)}
  var Icon = Class.extend({
    options: {
      popupAnchor: [0, 0],
      tooltipAnchor: [0, 0]
    },
    initialize: function (options) {setOptions(this, options)},
    createIcon: function (oldIcon) {return this._createIcon('icon', oldIcon)},
    createShadow: function (oldIcon) {
      return this._createIcon('shadow', oldIcon)
    },
    _createIcon: function (name, oldIcon) {
      var src = this._getIconUrl(name)
      if (!src) {
        if (name === 'icon') {
          throw new Error('iconUrl not set in Icon options (see the docs).')
        }
        return null
      }
      var img = this._createImg(src,
        oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null)
      this._setIconStyles(img, name)
      return img
    },
    _setIconStyles: function (img, name) {
      var options = this.options
      var sizeOption = options[name + 'Size']
      if (typeof sizeOption === 'number') {
        sizeOption = [
          sizeOption,
          sizeOption
        ]
      }
      var size = toPoint(sizeOption)
      var anchor = toPoint(
        name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
        size && size.divideBy(2, true))
      img.className = 'winfo-marker-' + name + ' ' + (options.className || '')
      if (anchor) {
        img.style.marginLeft = (-anchor.x) + 'px'
        img.style.marginTop = (-anchor.y) + 'px'
      }
      if (size) {
        img.style.width = size.x + 'px'
        img.style.height = size.y + 'px'
      }
    },
    _createImg: function (src, el) {
      el = el || document.createElement('img')
      el.src = src
      return el
    },
    _getIconUrl: function (name) {
      return retina && this.options[name + 'RetinaUrl'] ||
        this.options[name + 'Url']
    }
  })

  function icon (options) {return new Icon(options)}

  var IconDefault = Icon.extend({
    options: {
      iconUrl: '',
      iconRetinaUrl: '',
      shadowUrl: '',
      iconSize: [25, 41],
      iconAnchor: [12, 41],
      popupAnchor: [1, -34],
      tooltipAnchor: [16, -28],
      shadowSize: [41, 41]
    },
    _getIconUrl: function (name) {
      if (!IconDefault.imagePath) {IconDefault.imagePath = this._detectIconPath()}
      return Icon.prototype._getIconUrl.call(this, name)
    },
    _detectIconPath: function () {
      var el = create$1('div', 'winfo-default-icon-path', document.body)
      var path = getStyle(el, 'background-image') ||
        getStyle(el, 'backgroundImage')
      document.body.removeChild(el)
      if (path === null || path.indexOf('url') !== 0) {path = ''} else {
        path = path.replace(/^url\(["']?/, '').
        replace(/marker-icon\.png["']?\)$/, '')
      }
      return path
    }
  })
  var MarkerDrag = Handler.extend({
    initialize: function (marker) {this._marker = marker},
    addHooks: function () {
      var icon = this._marker._icon
      if (!this._draggable) {this._draggable = new Draggable(icon, icon, true)}
      this._draggable.on({
        dragstart: this._onDragStart,
        predrag: this._onPreDrag,
        drag: this._onDrag,
        dragend: this._onDragEnd
      }, this).enable()
      addClass(icon, 'winfo-marker-draggable')
    },
    removeHooks: function () {
      this._draggable.off({
        dragstart: this._onDragStart,
        predrag: this._onPreDrag,
        drag: this._onDrag,
        dragend: this._onDragEnd
      }, this).disable()
      if (this._marker._icon) {
        removeClass(this._marker._icon, 'winfo-marker-draggable')
      }
    },
    moved: function () {return this._draggable && this._draggable._moved},
    _adjustPan: function (e) {
      var marker = this._marker
      var map = marker._map
      var speed = this._marker.options.autoPanSpeed
      var padding = this._marker.options.autoPanPadding
      var iconPos = getPosition(marker._icon)
      var bounds = map.getPixelBounds()
      var origin = map.getPixelOrigin()
      var panBounds = toBounds(bounds.min._subtract(origin).add(padding),
        bounds.max._subtract(origin).subtract(padding))
      if (!panBounds.contains(iconPos)) {
        var movement = toPoint(
          (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) /
          (bounds.max.x - panBounds.max.x) -
          (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) /
          (bounds.min.x - panBounds.min.x),
          (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) /
          (bounds.max.y - panBounds.max.y) -
          (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) /
          (bounds.min.y - panBounds.min.y)).multiplyBy(speed)
        map.panBy(movement, { animate: false })
        this._draggable._newPos._add(movement)
        this._draggable._startPos._add(movement)
        setPosition(marker._icon, this._draggable._newPos)
        this._onDrag(e)
        this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e))
      }
    },
    _onDragStart: function () {
      this._oldLatLng = this._marker.getLatLng()
      this._marker.closePopup().fire('movestart').fire('dragstart')
    },
    _onPreDrag: function (e) {
      if (this._marker.options.autoPan) {
        cancelAnimFrame(this._panRequest)
        this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e))
      }
    },
    _onDrag: function (e) {
      var marker = this._marker
      var shadow = marker._shadow
      var iconPos = getPosition(marker._icon)
      var latlng = marker._map.layerPointToLatLng(iconPos)
      if (shadow) {setPosition(shadow, iconPos)}
      marker._latlng = latlng
      e.latlng = latlng
      e.oldLatLng = this._oldLatLng
      marker.fire('move', e).fire('drag', e)
    },
    _onDragEnd: function (e) {
      cancelAnimFrame(this._panRequest)
      delete this._oldLatLng
      this._marker.fire('moveend').fire('dragend', e)
    }
  })
  var Marker = Layer.extend({
    options: {
      icon: new IconDefault(),
      interactive: true,
      keyboard: true,
      title: '',
      alt: '',
      zIndexOffset: 0,
      opacity: 1,
      riseOnHover: false,
      riseOffset: 250,
      pane: 'markerPane',
      shadowPane: 'shadowPane',
      bubblingMouseEvents: false,
      draggable: false,
      autoPan: false,
      autoPanPadding: [50, 50],
      autoPanSpeed: 10
    },
    initialize: function (latlng, options) {
      setOptions(this, options)
      this._latlng = toLatLng(latlng)
    },
    onAdd: function (map) {
      this._zoomAnimated = this._zoomAnimated &&
        map.options.markerZoomAnimation
      if (this._zoomAnimated) {map.on('zoomanim', this._animateZoom, this)}
      this._initIcon()
      this.update()
    },
    onRemove: function (map) {
      if (this.dragging && this.dragging.enabled()) {
        this.options.draggable = true
        this.dragging.removeHooks()
      }
      delete this.dragging
      if (this._zoomAnimated) {map.off('zoomanim', this._animateZoom, this)}
      this._removeIcon()
      this._removeShadow()
    },
    getEvents: function () {
      return {
        zoom: this.update,
        viewreset: this.update
      }
    },
    getLatLng: function () {return this._latlng},
    setLatLng: function (latlng) {
      var oldLatLng = this._latlng
      this._latlng = toLatLng(latlng)
      this.update()
      return this.fire('move', {
        oldLatLng: oldLatLng,
        latlng: this._latlng
      })
    },
    setZIndexOffset: function (offset) {
      this.options.zIndexOffset = offset
      return this.update()
    },
    getIcon: function () {return this.options.icon},
    setIcon: function (icon) {
      this.options.icon = icon
      if (this._map) {
        this._initIcon()
        this.update()
      }
      if (this._popup) {this.bindPopup(this._popup, this._popup.options)}
      return this
    },
    getElement: function () {return this._icon},
    update: function () {
      if (this._icon && this._map) {
        var pos = this._map.latLngToLayerPoint(this._latlng).round()
        this._setPos(pos)
      }
      return this
    },
    _initIcon: function () {
      var options = this.options
      var classToAdd = 'winfo-zoom-' +
        (this._zoomAnimated ? 'animated' : 'hide')
      var icon = options.icon.createIcon(this._icon)
      var addIcon = false
      if (icon !== this._icon) {
        if (this._icon) {this._removeIcon()}
        addIcon = true
        if (options.title) {icon.title = options.title}
        if (icon.tagName === 'IMG') {icon.alt = options.alt || ''}
      }
      addClass(icon, classToAdd)
      if (options.keyboard) {icon.tabIndex = '0'}
      this._icon = icon
      if (options.riseOnHover) {
        this.on({
          mouseover: this._bringToFront,
          mouseout: this._resetZIndex
        })
      }
      var newShadow = options.icon.createShadow(this._shadow)
      var addShadow = false
      if (newShadow !== this._shadow) {
        this._removeShadow()
        addShadow = true
      }
      if (newShadow) {
        addClass(newShadow, classToAdd)
        newShadow.alt = ''
      }
      this._shadow = newShadow
      if (options.opacity < 1) {this._updateOpacity()}
      if (addIcon) {this.getPane().appendChild(this._icon)}
      this._initInteraction()
      if (newShadow && addShadow) {
        this.getPane(options.shadowPane).
        appendChild(this._shadow)
      }
    },
    _removeIcon: function () {
      if (this.options.riseOnHover) {
        this.off({
          mouseover: this._bringToFront,
          mouseout: this._resetZIndex
        })
      }
      remove(this._icon)
      this.removeInteractiveTarget(this._icon)
      this._icon = null
    },
    _removeShadow: function () {
      if (this._shadow) {remove(this._shadow)}
      this._shadow = null
    },
    _setPos: function (pos) {
      if (this._icon) {setPosition(this._icon, pos)}
      if (this._shadow) {setPosition(this._shadow, pos)}
      this._zIndex = pos.y + this.options.zIndexOffset
      this._resetZIndex()
    },
    _updateZIndex: function (offset) {
      if (this._icon) {
        this._icon.style.zIndex = this._zIndex + offset
      }
    },
    _animateZoom: function (opt) {
      var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom,
        opt.center).round()
      this._setPos(pos)
    },
    _initInteraction: function () {
      if (!this.options.interactive) {return}
      addClass(this._icon, 'winfo-interactive')
      this.addInteractiveTarget(this._icon)
      if (MarkerDrag) {
        var draggable = this.options.draggable
        if (this.dragging) {
          draggable = this.dragging.enabled()
          this.dragging.disable()
        }
        this.dragging = new MarkerDrag(this)
        if (draggable) {this.dragging.enable()}
      }
    },
    setOpacity: function (opacity) {
      this.options.opacity = opacity
      if (this._map) {this._updateOpacity()}
      return this
    },
    _updateOpacity: function () {
      var opacity = this.options.opacity
      if (this._icon) {setOpacity(this._icon, opacity)}
      if (this._shadow) {setOpacity(this._shadow, opacity)}
    },
    _bringToFront: function () {this._updateZIndex(this.options.riseOffset)},
    _resetZIndex: function () {this._updateZIndex(0)},
    _getPopupAnchor: function () {return this.options.icon.options.popupAnchor},
    _getTooltipAnchor: function () {return this.options.icon.options.tooltipAnchor}
  })
  var proto_initIcon = Marker.prototype._initIcon
  var proto_setPos = Marker.prototype._setPos
  var oldIE = (DomUtil.TRANSFORM === 'msTransform')
  Marker.addInitHook(function () {
    var iconOptions = this.options.icon && this.options.icon.options
    var iconAnchor = iconOptions && this.options.icon.options.iconAnchor
    if (iconAnchor) {
      iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px')
    }
    this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor ||
      'center bottom'
    this.options.rotationAngle = this.options.rotationAngle || 0
    this.on('drag', function (e) {e.target._applyRotation()})
  })
  Marker.include({
    _initIcon: function () {proto_initIcon.call(this)},
    _setPos: function (pos) {
      proto_setPos.call(this, pos)
      this._applyRotation()
    },
    _applyRotation: function () {
      if (this.options.rotationAngle) {
        this._icon.style[DomUtil.TRANSFORM +
        'Origin'] = this.options.rotationOrigin
        if (oldIE) {
          this._icon.style[DomUtil.TRANSFORM] = 'rotate(' +
            this.options.rotationAngle + 'deg)'
        } else {
          this._icon.style[DomUtil.TRANSFORM] += ' rotateZ(' +
            this.options.rotationAngle + 'deg)'
        }
      }
    },
    setRotationAngle: function (angle) {
      this.options.rotationAngle = angle
      this.update()
      return this
    },
    setRotationOrigin: function (origin) {
      this.options.rotationOrigin = origin
      this.update()
      return this
    }
  })

  function marker (latlng, options) {return new Marker(latlng, options)}

  var Path = Layer.extend({
    options: {
      stroke: true,
      color: '#3388ff',
      weight: 3,
      opacity: 1,
      lineCap: 'round',
      lineJoin: 'round',
      dashArray: null,
      dashOffset: null,
      fill: false,
      fillColor: null,
      fillOpacity: 0.2,
      fillRule: 'evenodd',
      interactive: true,
      bubblingMouseEvents: true
    },
    beforeAdd: function (map) {this._renderer = map.getRenderer(this)},
    onAdd: function () {
      this._renderer._initPath(this)
      this._reset()
      this._renderer._addPath(this)
    },
    onRemove: function () {this._renderer._removePath(this)},
    redraw: function () {
      if (this._map) {this._renderer._updatePath(this)}
      return this
    },
    setStyle: function (style) {
      setOptions(this, style)
      if (this._renderer) {
        this._renderer._updateStyle(this)
        if (this.options.stroke && style &&
          style.hasOwnProperty('weight')) {this._updateBounds()}
      }
      return this
    },
    bringToFront: function () {
      if (this._renderer) {
        this._renderer._bringToFront(this)
      }
      return this
    },
    bringToBack: function () {
      if (this._renderer) {
        this._renderer._bringToBack(this)
      }
      return this
    },
    getElement: function () {return this._path},
    _reset: function () {
      this._project()
      this._update()
    },
    _clickTolerance: function () {
      return (this.options.stroke
        ? this.options.weight / 2
        : 0) + this._renderer.options.tolerance
    }
  })
  var CircleMarker = Path.extend({
    options: {
      fill: true,
      radius: 10
    },
    initialize: function (latlng, options) {
      setOptions(this, options)
      this._latlng = toLatLng(latlng)
      this._radius = this.options.radius
    },
    setLatLng: function (latlng) {
      var oldLatLng = this._latlng
      this._latlng = toLatLng(latlng)
      this.redraw()
      return this.fire('move', {
        oldLatLng: oldLatLng,
        latlng: this._latlng
      })
    },
    getLatLng: function () {return this._latlng},
    setRadius: function (radius) {
      this.options.radius = this._radius = radius
      return this.redraw()
    },
    getRadius: function () {return this._radius},
    setStyle: function (options) {
      var radius = options && options.radius || this._radius
      Path.prototype.setStyle.call(this, options)
      this.setRadius(radius)
      return this
    },
    _project: function () {
      this._point = this._map.latLngToLayerPoint(this._latlng)
      this._updateBounds()
    },
    _updateBounds: function () {
      var r = this._radius
      var r2 = this._radiusY || r
      var w = this._clickTolerance()
      var p = [r + w, r2 + w]
      this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p))
    },
    _update: function () {if (this._map) {this._updatePath()}},
    _updatePath: function () {this._renderer._updateCircle(this)},
    _empty: function () {
      return this._radius && !this._renderer._bounds.intersects(this._pxBounds)
    },
    _containsPoint: function (p) {
      return p.distanceTo(this._point) <= this._radius + this._clickTolerance()
    }
  })

  function circleMarker (latlng, options) {
    return new CircleMarker(latlng, options)
  }

  var Circle = CircleMarker.extend({
    initialize: function (latlng, options, legacyOptions) {
      if (typeof options === 'number') {
        options = extend({}, legacyOptions, { radius: options })
      }
      setOptions(this, options)
      this._latlng = toLatLng(latlng)
      if (isNaN(this.options.radius)) {
        throw new Error('Circle radius cannot be NaN')
      }
      this._mRadius = this.options.radius
    },
    setRadius: function (radius) {
      this._mRadius = radius
      return this.redraw()
    },
    getRadius: function () {return this._mRadius},
    getBounds: function () {
      var half = [
        this._radius,
        this._radiusY || this._radius
      ]
      return new LatLngBounds(
        this._map.layerPointToLatLng(this._point.subtract(half)),
        this._map.layerPointToLatLng(this._point.add(half)))
    },
    setStyle: Path.prototype.setStyle,
    _project: function () {
      var lng = this._latlng.lng
      var lat = this._latlng.lat
      var map = this._map
      var crs = map.options.crs
      if (crs.distance === Earth.distance) {
        var d = Math.PI / 180
        var latR = (this._mRadius / Earth.R) / d
        var top = map.project([lat + latR, lng])
        var bottom = map.project([lat - latR, lng])
        var p = top.add(bottom).divideBy(2)
        var lat2 = map.unproject(p).lat
        var lngR = Math.acos(
          (Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
          (Math.cos(lat * d) * Math.cos(lat2 * d))) / d
        if (isNaN(lngR) || lngR === 0) {
          lngR = latR / Math.cos(Math.PI / 180 * lat)
        }
        this._point = p.subtract(map.getPixelOrigin())
        this._radius = isNaN(lngR) ? 0 : p.x -
          map.project([lat2, lng - lngR]).x
        this._radiusY = p.y - top.y
      } else {
        var latlng2 = crs.unproject(
          crs.project(this._latlng).subtract([this._mRadius, 0]))
        this._point = map.latLngToLayerPoint(this._latlng)
        this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x
      }
      this._updateBounds()
    }
  })

  function circle (latlng, options, legacyOptions) {
    return new Circle(latlng, options, legacyOptions)
  }

  var Polyline = Path.extend({
    options: {
      smoothFactor: 1.0,
      noClip: false
    },
    initialize: function (latlngs, options) {
      setOptions(this, options)
      this._setLatLngs(latlngs)
    },
    getLatLngs: function () {return this._latlngs},
    setLatLngs: function (latlngs) {
      this._setLatLngs(latlngs)
      return this.redraw()
    },
    isEmpty: function () {return !this._latlngs.length},
    closestLayerPoint: function (p) {
      var minDistance = Infinity
      var minPoint = null
      var closest = _sqClosestPointOnSegment
      var p1
      var p2
      for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
        var points = this._parts[j]
        for (var i = 1, len = points.length; i < len; i++) {
          p1 = points[i - 1]
          p2 = points[i]
          var sqDist = closest(p, p1, p2, true)
          if (sqDist < minDistance) {
            minDistance = sqDist
            minPoint = closest(p, p1, p2)
          }
        }
      }
      if (minPoint) {minPoint.distance = Math.sqrt(minDistance)}
      return minPoint
    },
    getCenter: function () {
      if (!this._map) {
        throw new Error('Must add layer to map before using getCenter()')
      }
      var i
      var halfDist
      var segDist
      var dist
      var p1
      var p2
      var ratio
      var points = this._rings[0]
      var len = points.length
      if (!len) {return null}
      for (i = 0, halfDist = 0; i < len -
      1; i++) {halfDist += points[i].distanceTo(points[i + 1]) / 2}
      if (halfDist === 0) {return this._map.layerPointToLatLng(points[0])}
      for (i = 0, dist = 0; i < len - 1; i++) {
        p1 = points[i]
        p2 = points[i + 1]
        segDist = p1.distanceTo(p2)
        dist += segDist
        if (dist > halfDist) {
          ratio = (dist - halfDist) / segDist
          return this._map.layerPointToLatLng(
            [p2.x - ratio * (p2.x - p1.x), p2.y - ratio * (p2.y - p1.y)])
        }
      }
    },
    getBounds: function () {return this._bounds},
    addLatLng: function (latlng, latlngs) {
      latlngs = latlngs || this._defaultShape()
      latlng = toLatLng(latlng)
      latlngs.push(latlng)
      this._bounds.extend(latlng)
      return this.redraw()
    },
    _setLatLngs: function (latlngs) {
      this._bounds = new LatLngBounds()
      this._latlngs = this._convertLatLngs(latlngs)
    },
    _defaultShape: function () {
      return isFlat(this._latlngs)
        ? this._latlngs
        : this._latlngs[0]
    },
    _convertLatLngs: function (latlngs) {
      var result = []
      var flat = isFlat(latlngs)
      for (var i = 0, len = latlngs.length; i < len; i++) {
        if (flat) {
          result[i] = toLatLng(latlngs[i])
          this._bounds.extend(result[i])
        } else {result[i] = this._convertLatLngs(latlngs[i])}
      }
      return result
    },
    _project: function () {
      var pxBounds = new Bounds()
      this._rings = []
      this._projectLatlngs(this._latlngs, this._rings, pxBounds)
      if (this._bounds.isValid() && pxBounds.isValid()) {
        this._rawPxBounds = pxBounds
        this._updateBounds()
      }
    },
    _updateBounds: function () {
      var w = this._clickTolerance()
      var p = new Point(w, w)
      this._pxBounds = new Bounds(
        [this._rawPxBounds.min.subtract(p), this._rawPxBounds.max.add(p)])
    },
    _projectLatlngs: function (latlngs, result, projectedBounds) {
      var flat = latlngs[0] instanceof LatLng
      var len = latlngs.length
      var i
      var ring
      if (flat) {
        ring = []
        for (i = 0; i < len; i++) {
          ring[i] = this._map.latLngToLayerPoint(latlngs[i])
          projectedBounds.extend(ring[i])
        }
        result.push(ring)
      } else {
        for (i = 0; i < len; i++) {
          this._projectLatlngs(latlngs[i], result, projectedBounds)
        }
      }
    },
    _clipPoints: function () {
      var bounds = this._renderer._bounds
      this._parts = []
      if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {return}
      if (this.options.noClip) {
        this._parts = this._rings
        return
      }
      var parts = this._parts
      var i
      var j
      var k
      var len
      var len2
      var segment
      var points
      for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
        points = this._rings[i]
        for (j = 0, len2 = points.length; j < len2 - 1; j++) {
          segment = clipSegment(points[j], points[j + 1], bounds, j, true)
          if (!segment) {continue}
          parts[k] = parts[k] || []
          parts[k].push(segment[0])
          if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
            parts[k].push(segment[1])
            k++
          }
        }
      }
    },
    _simplifyPoints: function () {
      var parts = this._parts
      var tolerance = this.options.smoothFactor
      for (var i = 0, len = parts.length; i < len; i++) {
        parts[i] = simplify(parts[i], tolerance)
      }
    },
    _update: function () {
      if (!this._map) {return}
      this._clipPoints()
      this._simplifyPoints()
      this._updatePath()
    },
    _updatePath: function () {this._renderer._updatePoly(this)},
    _containsPoint: function (p, closed) {
      var i
      var j
      var k
      var len
      var len2
      var part
      var w = this._clickTolerance()
      if (!this._pxBounds || !this._pxBounds.contains(p)) {return false}
      for (i = 0, len = this._parts.length; i < len; i++) {
        part = this._parts[i]
        for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
          if (!closed && (j === 0)) {continue}
          if (pointToSegmentDistance(p, part[k], part[j]) <= w) {return true}
        }
      }
      return false
    }
  })

  function polyline (latlngs, options) {return new Polyline(latlngs, options)}

  Polyline._flat = _flat
  var Polygon = Polyline.extend({
    options: { fill: true },
    isEmpty: function () {
      return !this._latlngs.length || !this._latlngs[0].length
    },
    getCenter: function () {
      if (!this._map) {
        throw new Error('Must add layer to map before using getCenter()')
      }
      var i
      var j
      var p1
      var p2
      var f
      var area
      var x
      var y
      var center
      var points = this._rings[0]
      var len = points.length
      if (!len) {return null}
      area = x = y = 0
      for (i = 0, j = len - 1; i < len; j = i++) {
        p1 = points[i]
        p2 = points[j]
        f = p1.y * p2.x - p2.y * p1.x
        x += (p1.x + p2.x) * f
        y += (p1.y + p2.y) * f
        area += f * 3
      }
      if (area === 0) {center = points[0]} else {
        center = [
          x / area,
          y / area
        ]
      }
      return this._map.layerPointToLatLng(center)
    },
    _convertLatLngs: function (latlngs) {
      var result = Polyline.prototype._convertLatLngs.call(this, latlngs)
      var len = result.length
      if (len >= 2 && result[0] instanceof LatLng &&
        result[0].equals(result[len - 1])) {result.pop()}
      return result
    },
    _setLatLngs: function (latlngs) {
      Polyline.prototype._setLatLngs.call(this, latlngs)
      if (isFlat(this._latlngs)) {this._latlngs = [this._latlngs]}
    },
    _defaultShape: function () {
      return isFlat(this._latlngs[0])
        ? this._latlngs[0]
        : this._latlngs[0][0]
    },
    _clipPoints: function () {
      var bounds = this._renderer._bounds
      var w = this.options.weight
      var p = new Point(w, w)
      bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p))
      this._parts = []
      if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {return}
      if (this.options.noClip) {
        this._parts = this._rings
        return
      }
      for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
        clipped = clipPolygon(this._rings[i], bounds, true)
        if (clipped.length) {this._parts.push(clipped)}
      }
    },
    _updatePath: function () {this._renderer._updatePoly(this, true)},
    _containsPoint: function (p) {
      var inside = false
      var part
      var p1
      var p2
      var i
      var j
      var k
      var len
      var len2
      if (!this._pxBounds || !this._pxBounds.contains(p)) {return false}
      for (i = 0, len = this._parts.length; i < len; i++) {
        part = this._parts[i]
        for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
          p1 = part[j]
          p2 = part[k]
          if (((p1.y > p.y) !== (p2.y > p.y)) &&
            (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) +
              p1.x)) {inside = !inside}
        }
      }
      return inside || Polyline.prototype._containsPoint.call(this, p, true)
    }
  })

  function polygon (latlngs, options) {return new Polygon(latlngs, options)}

  var GeoJSON = FeatureGroup.extend({
    initialize: function (geojson, options) {
      setOptions(this, options)
      this._layers = {}
      if (geojson) {this.addData(geojson)}
    },
    addData: function (geojson) {
      var features = isArray(geojson)
        ? geojson
        : geojson.features
      var i
      var len
      var feature
      if (features) {
        for (i = 0, len = features.length; i < len; i++) {
          feature = features[i]
          if (feature.geometries || feature.geometry || feature.features ||
            feature.coordinates) {this.addData(feature)}
        }
        return this
      }
      var options = this.options
      if (options.filter && !options.filter(geojson)) {return this}
      var layer = geometryToLayer(geojson, options)
      if (!layer) {return this}
      layer.feature = asFeature(geojson)
      layer.defaultOptions = layer.options
      this.resetStyle(layer)
      if (options.onEachFeature) {options.onEachFeature(geojson, layer)}
      return this.addLayer(layer)
    },
    resetStyle: function (layer) {
      if (layer === undefined) {return this.eachLayer(this.resetStyle, this)}
      layer.options = extend({}, layer.defaultOptions)
      this._setLayerStyle(layer, this.options.style)
      return this
    },
    setStyle: function (style) {
      return this.eachLayer(
        function (layer) {this._setLayerStyle(layer, style)},
        this)
    },
    _setLayerStyle: function (layer, style) {
      if (layer.setStyle) {
        if (typeof style === 'function') {style = style(layer.feature)}
        layer.setStyle(style)
      }
    }
  })

  function geometryToLayer (geojson, options) {
    var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
    var coords = geometry ? geometry.coordinates : null
    var layers = []
    var pointToLayer = options && options.pointToLayer
    var _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng
    var latlng
    var latlngs
    var i
    var len
    if (!coords && !geometry) {return null}
    switch (geometry.type) {
      case'Point':
        latlng = _coordsToLatLng(coords)
        return _pointToLayer(pointToLayer, geojson, latlng, options)
      case'MultiPoint':
        for (i = 0, len = coords.length; i < len; i++) {
          latlng = _coordsToLatLng(coords[i])
          layers.push(_pointToLayer(pointToLayer, geojson, latlng, options))
        }
        return new FeatureGroup(layers)
      case'LineString':
      case'MultiLineString':
        latlngs = coordsToLatLngs(coords,
          geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng)
        return new Polyline(latlngs, options)
      case'Polygon':
      case'MultiPolygon':
        latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2,
          _coordsToLatLng)
        return new Polygon(latlngs, options)
      case'GeometryCollection':
        for (i = 0, len = geometry.geometries.length; i < len; i++) {
          var layer = geometryToLayer({
            geometry: geometry.geometries[i],
            type: 'Feature',
            properties: geojson.properties
          }, options)
          if (layer) {layers.push(layer)}
        }
        return new FeatureGroup(layers)
      default:
        throw new Error('Invalid GeoJSON object.')
    }
  }

  function _pointToLayer (
    pointToLayerFn, geojson, latlng, options) {
    return pointToLayerFn ? pointToLayerFn(geojson, latlng) : new Marker(latlng,
      options && options.markersInheritOptions && options)
  }

  function coordsToLatLng (coords) {
    return new LatLng(coords[1], coords[0], coords[2])
  }

  function coordsToLatLngs (
    coords, levelsDeep, _coordsToLatLng) {
    var latlngs = []
    for (var i = 0, len = coords.length, latlng; i < len; i++) {
      latlng = levelsDeep ? coordsToLatLngs(coords[i], levelsDeep - 1,
        _coordsToLatLng) : (_coordsToLatLng || coordsToLatLng)(coords[i])
      latlngs.push(latlng)
    }
    return latlngs
  }

  function latLngToCoords (latlng, precision) {
    precision = typeof precision === 'number' ? precision : 6
    return latlng.alt !== undefined ? [
      formatNum(latlng.lng, precision),
      formatNum(latlng.lat, precision),
      formatNum(latlng.alt, precision)
    ] : [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]
  }

  function latLngsToCoords (
    latlngs, levelsDeep, closed, precision) {
    var coords = []
    for (var i = 0, len = latlngs.length; i < len; i++) {
      coords.push(
        levelsDeep ? latLngsToCoords(latlngs[i], levelsDeep - 1, closed,
          precision) : latLngToCoords(latlngs[i], precision))
    }
    if (!levelsDeep && closed) {coords.push(coords[0])}
    return coords
  }

  function getFeature (layer, newGeometry) {
    return layer.feature
      ? extend({}, layer.feature, { geometry: newGeometry })
      : asFeature(newGeometry)
  }

  function asFeature (geojson) {
    if (geojson.type === 'Feature' || geojson.type ===
      'FeatureCollection') {return geojson}
    return {
      type: 'Feature',
      properties: {},
      geometry: geojson
    }
  }

  var PointToGeoJSON = {
    toGeoJSON: function (precision) {
      return getFeature(this, {
        type: 'Point',
        coordinates: latLngToCoords(this.getLatLng(), precision)
      })
    }
  }
  Marker.include(PointToGeoJSON)
  Circle.include(PointToGeoJSON)
  CircleMarker.include(PointToGeoJSON)
  Polyline.include({
    toGeoJSON: function (precision) {
      var multi = !isFlat(this._latlngs)
      var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false,
        precision)
      return getFeature(this, {
        type: (multi ? 'Multi' : '') + 'LineString',
        coordinates: coords
      })
    }
  })
  Polygon.include({
    toGeoJSON: function (precision) {
      var holes = !isFlat(this._latlngs)
      var multi = holes && !isFlat(this._latlngs[0])
      var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0,
        true, precision)
      if (!holes) {coords = [coords]}
      return getFeature(this, {
        type: (multi ? 'Multi' : '') + 'Polygon',
        coordinates: coords
      })
    }
  })
  LayerGroup.include({
    toMultiPoint: function (precision) {
      var coords = []
      this.eachLayer(function (layer) {
        coords.push(layer.toGeoJSON(precision).geometry.coordinates)
      })
      return getFeature(this, {
        type: 'MultiPoint',
        coordinates: coords
      })
    },
    toGeoJSON: function (precision) {
      var type = this.feature && this.feature.geometry &&
        this.feature.geometry.type
      if (type === 'MultiPoint') {return this.toMultiPoint(precision)}
      var isGeometryCollection = type === 'GeometryCollection'
      var jsons = []
      this.eachLayer(function (layer) {
        if (layer.toGeoJSON) {
          var json = layer.toGeoJSON(precision)
          if (isGeometryCollection) {
            jsons.push(json.geometry)
          } else {
            var feature = asFeature(json)
            if (feature.type === 'FeatureCollection') {
              jsons.push.apply(jsons, feature.features)
            } else {jsons.push(feature)}
          }
        }
      })
      if (isGeometryCollection) {
        return getFeature(this, {
          geometries: jsons,
          type: 'GeometryCollection'
        })
      }
      return {
        type: 'FeatureCollection',
        features: jsons
      }
    }
  })

  function geoJSON (geojson, options) {return new GeoJSON(geojson, options)}

  var geoJson = geoJSON
  var ImageOverlay = Layer.extend({
    options: {
      opacity: 1,
      alt: '',
      interactive: false,
      crossOrigin: false,
      errorOverlayUrl: '',
      zIndex: 1,
      className: ''
    },
    initialize: function (url, bounds, options) {
      this._url = url
      this._bounds = toLatLngBounds(bounds)
      setOptions(this, options)
    },
    onAdd: function () {
      if (!this._image) {
        this._initImage()
        if (this.options.opacity < 1) {this._updateOpacity()}
      }
      if (this.options.interactive) {
        addClass(this._image, 'winfo-interactive')
        this.addInteractiveTarget(this._image)
      }
      this.getPane().appendChild(this._image)
      this._reset()
    },
    onRemove: function () {
      remove(this._image)
      if (this.options.interactive) {this.removeInteractiveTarget(this._image)}
    },
    setOpacity: function (opacity) {
      this.options.opacity = opacity
      if (this._image) {this._updateOpacity()}
      return this
    },
    setStyle: function (styleOpts) {
      if (styleOpts.opacity) {
        this.setOpacity(styleOpts.opacity)
      }
      return this
    },
    bringToFront: function () {
      if (this._map) {toFront(this._image)}
      return this
    },
    bringToBack: function () {
      if (this._map) {toBack(this._image)}
      return this
    },
    setUrl: function (url) {
      this._url = url
      if (this._image) {this._image.src = url}
      return this
    },
    setBounds: function (bounds) {
      this._bounds = toLatLngBounds(bounds)
      if (this._map) {this._reset()}
      return this
    },
    getEvents: function () {
      var events = {
        zoom: this._reset,
        viewreset: this._reset
      }
      if (this._zoomAnimated) {events.zoomanim = this._animateZoom}
      return events
    },
    setZIndex: function (value) {
      this.options.zIndex = value
      this._updateZIndex()
      return this
    },
    getBounds: function () {return this._bounds},
    getElement: function () {return this._image},
    _initImage: function () {
      var wasElementSupplied = this._url.tagName === 'IMG'
      var img = this._image = wasElementSupplied ? this._url : create$1('img')
      addClass(img, 'winfo-image-layer')
      if (this._zoomAnimated) {addClass(img, 'winfo-zoom-animated')}
      if (this.options.className) {addClass(img, this.options.className)}
      img.onselectstart = falseFn
      img.onmousemove = falseFn
      img.onload = bind(this.fire, this, 'load')
      img.onerror = bind(this._overlayOnError, this, 'error')
      if (this.options.crossOrigin || this.options.crossOrigin === '') {
        img.crossOrigin = this.options.crossOrigin === true
          ? ''
          : this.options.crossOrigin
      }
      if (this.options.zIndex) {this._updateZIndex()}
      if (wasElementSupplied) {
        this._url = img.src
        return
      }
      img.src = this._url
      img.alt = this.options.alt
    },
    _animateZoom: function (e) {
      var scale = this._map.getZoomScale(e.zoom)
      var offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom,
        e.center).min
      setTransform(this._image, offset, scale)
    },
    _reset: function () {
      var image = this._image
      var bounds = new Bounds(
        this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
        this._map.latLngToLayerPoint(this._bounds.getSouthEast()))
      var size = bounds.getSize()
      setPosition(image, bounds.min)
      image.style.width = size.x + 'px'
      image.style.height = size.y + 'px'
    },
    _updateOpacity: function () {
      setOpacity(this._image, this.options.opacity)
    },
    _updateZIndex: function () {
      if (this._image && this.options.zIndex !== undefined &&
        this.options.zIndex !==
        null) {this._image.style.zIndex = this.options.zIndex}
    },
    _overlayOnError: function () {
      this.fire('error')
      var errorUrl = this.options.errorOverlayUrl
      if (errorUrl && this._url !== errorUrl) {
        this._url = errorUrl
        this._image.src = errorUrl
      }
    }
  })
  var imageOverlay = function (url, bounds, options) {
    return new ImageOverlay(url, bounds, options)
  }
  var VideoOverlay = ImageOverlay.extend({
    options: {
      autoplay: true,
      loop: true,
      keepAspectRatio: true
    },
    _initImage: function () {
      var wasElementSupplied = this._url.tagName === 'VIDEO'
      var vid = this._image = wasElementSupplied ? this._url : create$1(
        'video')
      addClass(vid, 'winfo-image-layer')
      if (this._zoomAnimated) {addClass(vid, 'winfo-zoom-animated')}
      if (this.options.className) {addClass(vid, this.options.className)}
      vid.onselectstart = falseFn
      vid.onmousemove = falseFn
      vid.onloadeddata = bind(this.fire, this, 'load')
      if (wasElementSupplied) {
        var sourceElements = vid.getElementsByTagName('source')
        var sources = []
        for (var j = 0; j < sourceElements.length; j++) {
          sources.push(sourceElements[j].src)
        }
        this._url = (sourceElements.length > 0) ? sources : [vid.src]
        return
      }
      if (!isArray(this._url)) {this._url = [this._url]}
      if (!this.options.keepAspectRatio && vid.style.hasOwnProperty(
        'objectFit')) {vid.style['objectFit'] = 'fill'}
      vid.autoplay = !!this.options.autoplay
      vid.loop = !!this.options.loop
      for (var i = 0; i < this._url.length; i++) {
        var source = create$1('source')
        source.src = this._url[i]
        vid.appendChild(source)
      }
    }
  })

  function videoOverlay (video, bounds, options) {
    return new VideoOverlay(video, bounds, options)
  }

  var SVGOverlay = ImageOverlay.extend({
    _initImage: function () {
      var el = this._image = this._url
      addClass(el, 'winfo-image-layer')
      if (this._zoomAnimated) {addClass(el, 'winfo-zoom-animated')}
      if (this.options.className) {addClass(el, this.options.className)}
      el.onselectstart = falseFn
      el.onmousemove = falseFn
    }
  })

  function svgOverlay (el, bounds, options) {
    return new SVGOverlay(el, bounds, options)
  }

  var DivOverlay = Layer.extend({
    options: {
      offset: [0, 7],
      className: '',
      pane: 'popupPane'
    },
    initialize: function (options, source) {
      setOptions(this, options)
      this._source = source
    },
    onAdd: function (map) {
      this._zoomAnimated = map._zoomAnimated
      if (!this._container) {this._initLayout()}
      if (map._fadeAnimated) {setOpacity(this._container, 0)}
      clearTimeout(this._removeTimeout)
      this.getPane().appendChild(this._container)
      this.update()
      if (map._fadeAnimated) {setOpacity(this._container, 1)}
      this.bringToFront()
    },
    onRemove: function (map) {
      if (map._fadeAnimated) {
        setOpacity(this._container, 0)
        this._removeTimeout = setTimeout(
          bind(remove, undefined, this._container), 200)
      } else {remove(this._container)}
    },
    getLatLng: function () {return this._latlng},
    setLatLng: function (latlng) {
      this._latlng = toLatLng(latlng)
      if (this._map) {
        this._updatePosition()
        this._adjustPan()
      }
      return this
    },
    getContent: function () {return this._content},
    setContent: function (content) {
      this._content = content
      this.update()
      return this
    },
    getElement: function () {return this._container},
    update: function () {
      if (!this._map) {return}
      this._container.style.visibility = 'hidden'
      this._updateContent()
      this._updateLayout()
      this._updatePosition()
      this._container.style.visibility = ''
      this._adjustPan()
    },
    getEvents: function () {
      var events = {
        zoom: this._updatePosition,
        viewreset: this._updatePosition
      }
      if (this._zoomAnimated) {events.zoomanim = this._animateZoom}
      return events
    },
    isOpen: function () {return !!this._map && this._map.hasLayer(this)},
    bringToFront: function () {
      if (this._map) {toFront(this._container)}
      return this
    },
    bringToBack: function () {
      if (this._map) {toBack(this._container)}
      return this
    },
    _prepareOpen: function (parent, layer, latlng) {
      if (!(layer instanceof Layer)) {
        latlng = layer
        layer = parent
      }
      if (layer instanceof FeatureGroup) {
        for (var id in parent._layers) {
          layer = parent._layers[id]
          break
        }
      }
      if (!latlng) {
        if (layer.getCenter) {latlng = layer.getCenter()} else if (layer.getLatLng) {latlng = layer.getLatLng()} else {
          throw new Error('Unable to get source layer LatLng.')
        }
      }
      this._source = layer
      this.update()
      return latlng
    },
    _updateContent: function () {
      if (!this._content) {return}
      var node = this._contentNode
      var content = (typeof this._content === 'function') ? this._content(
        this._source || this) : this._content
      if (typeof content === 'string') {node.innerHTML = content} else {
        while (node.hasChildNodes()) {
          node.removeChild(node.firstChild)
        }
        node.appendChild(content)
      }
      this.fire('contentupdate')
    },
    _updatePosition: function () {
      if (!this._map) {return}
      var pos = this._map.latLngToLayerPoint(this._latlng)
      var offset = toPoint(this.options.offset)
      var anchor = this._getAnchor()
      if (this._zoomAnimated) {
        setPosition(this._container, pos.add(anchor))
      } else {offset = offset.add(pos).add(anchor)}
      var bottom = this._containerBottom = -offset.y
      var left = this._containerLeft = -Math.round(this._containerWidth / 2) +
        offset.x
      this._container.style.bottom = bottom + 'px'
      this._container.style.left = left + 'px'
    },
    _getAnchor: function () {return [0, 0]}
  })
  var Popup = DivOverlay.extend({
    options: {
      maxWidth: 300,
      minWidth: 50,
      maxHeight: null,
      autoPan: true,
      autoPanPaddingTopLeft: null,
      autoPanPaddingBottomRight: null,
      autoPanPadding: [5, 5],
      keepInView: false,
      closeButton: true,
      autoClose: true,
      closeOnEscapeKey: true,
      className: ''
    },
    openOn: function (map) {
      map.openPopup(this)
      return this
    },
    onAdd: function (map) {
      DivOverlay.prototype.onAdd.call(this, map)
      map.fire('popupopen', { popup: this })
      if (this._source) {
        this._source.fire('popupopen', { popup: this }, true)
        if (!(this._source instanceof Path)) {
          this._source.on('preclick', stopPropagation)
        }
      }
    },
    onRemove: function (map) {
      DivOverlay.prototype.onRemove.call(this, map)
      map.fire('popupclose', { popup: this })
      if (this._source) {
        this._source.fire('popupclose', { popup: this }, true)
        if (!(this._source instanceof Path)) {
          this._source.off('preclick', stopPropagation)
        }
      }
    },
    getEvents: function () {
      var events = DivOverlay.prototype.getEvents.call(this)
      if (this.options.closeOnClick !== undefined
        ? this.options.closeOnClick
        : this._map.options.closePopupOnClick) {events.preclick = this._close}
      if (this.options.keepInView) {events.moveend = this._adjustPan}
      return events
    },
    _close: function () {if (this._map) {this._map.closePopup(this)}},
    _initLayout: function () {
      var prefix = 'winfo-popup'
      var container = this._container = create$1('div',
        prefix + ' ' + (this.options.className || '') +
        ' winfo-zoom-animated')
      var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper',
        container)
      this._contentNode = create$1('div', prefix + '-content', wrapper)
      disableClickPropagation(wrapper)
      disableScrollPropagation(this._contentNode)
      on(wrapper, 'contextmenu', stopPropagation)
      this._tipContainer = create$1('div', prefix + '-tip-container',
        container)
      this._tip = create$1('div', prefix + '-tip', this._tipContainer)
      if (this.options.closeButton) {
        var closeButton = this._closeButton = create$1('a',
          prefix + '-close-button', container)
        closeButton.href = '#close'
        closeButton.innerHTML = '&#215;'
        on(closeButton, 'click', this._onCloseButtonClick, this)
      }
    },
    _updateLayout: function () {
      var container = this._contentNode
      var style = container.style
      style.width = ''
      style.whiteSpace = 'nowrap'
      var width = container.offsetWidth
      width = Math.min(width, this.options.maxWidth)
      width = Math.max(width, this.options.minWidth)
      style.width = (width + 1) + 'px'
      style.whiteSpace = ''
      style.height = ''
      var height = container.offsetHeight
      var maxHeight = this.options.maxHeight
      var scrolledClass = 'winfo-popup-scrolled'
      if (maxHeight && height > maxHeight) {
        style.height = maxHeight + 'px'
        addClass(container, scrolledClass)
      } else {removeClass(container, scrolledClass)}
      this._containerWidth = this._container.offsetWidth
    },
    _animateZoom: function (e) {
      var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom,
        e.center)
      var anchor = this._getAnchor()
      setPosition(this._container, pos.add(anchor))
    },
    _adjustPan: function () {
      if (!this.options.autoPan) {return}
      if (this._map._panAnim) {this._map._panAnim.stop()}
      var map = this._map
      var marginBottom = parseInt(getStyle(this._container, 'marginBottom'),
        10) || 0
      var containerHeight = this._container.offsetHeight + marginBottom
      var containerWidth = this._containerWidth
      var layerPos = new Point(this._containerLeft,
        -containerHeight - this._containerBottom)
      layerPos._add(getPosition(this._container))
      var containerPos = map.layerPointToContainerPoint(layerPos)
      var padding = toPoint(this.options.autoPanPadding)
      var paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding)
      var paddingBR = toPoint(
        this.options.autoPanPaddingBottomRight || padding)
      var size = map.getSize()
      var dx = 0
      var dy = 0
      if (containerPos.x + containerWidth + paddingBR.x >
        size.x) {dx = containerPos.x + containerWidth - size.x + paddingBR.x}
      if (containerPos.x - dx - paddingTL.x < 0) {
        dx = containerPos.x - paddingTL.x
      }
      if (containerPos.y + containerHeight + paddingBR.y >
        size.y) {
        dy = containerPos.y + containerHeight - size.y + paddingBR.y
      }
      if (containerPos.y - dy - paddingTL.y < 0) {
        dy = containerPos.y - paddingTL.y
      }
      if (dx || dy) {map.fire('autopanstart').panBy([dx, dy])}
    },
    _onCloseButtonClick: function (e) {
      this._close()
      stop(e)
    },
    _getAnchor: function () {
      return toPoint(this._source && this._source._getPopupAnchor
        ? this._source._getPopupAnchor()
        : [0, 0])
    }
  })
  var popup = function (options, source) {return new Popup(options, source)}
  Map.mergeOptions({ closePopupOnClick: true })
  Map.include({
    openPopup: function (popup, latlng, options) {
      if (!(popup instanceof Popup)) {
        popup = new Popup(options).setContent(popup)
      }
      if (latlng) {popup.setLatLng(latlng)}
      if (this.hasLayer(popup)) {return this}
      if (this._popup && this._popup.options.autoClose) {this.closePopup()}
      this._popup = popup
      return this.addLayer(popup)
    },
    closePopup: function (popup) {
      if (!popup || popup === this._popup) {
        popup = this._popup
        this._popup = null
      }
      if (popup) {this.removeLayer(popup)}
      return this
    }
  })
  Layer.include({
    bindPopup: function (content, options) {
      if (content instanceof Popup) {
        setOptions(content, options)
        this._popup = content
        content._source = this
      } else {
        if (!this._popup || options) {
          this._popup = new Popup(options, this)
        }
        this._popup.setContent(content)
      }
      if (!this._popupHandlersAdded) {
        this.on({
          click: this._openPopup,
          keypress: this._onKeyPress,
          remove: this.closePopup,
          move: this._movePopup
        })
        this._popupHandlersAdded = true
      }
      return this
    },
    unbindPopup: function () {
      if (this._popup) {
        this.off({
          click: this._openPopup,
          keypress: this._onKeyPress,
          remove: this.closePopup,
          move: this._movePopup
        })
        this._popupHandlersAdded = false
        this._popup = null
      }
      return this
    },
    openPopup: function (layer, latlng) {
      if (this._popup && this._map) {
        latlng = this._popup._prepareOpen(this, layer, latlng)
        this._map.openPopup(this._popup, latlng)
      }
      return this
    },
    closePopup: function () {
      if (this._popup) {this._popup._close()}
      return this
    },
    togglePopup: function (target) {
      if (this._popup) {
        if (this._popup._map) {this.closePopup()} else {
          this.openPopup(target)
        }
      }
      return this
    },
    isPopupOpen: function () {
      return (this._popup
        ? this._popup.isOpen()
        : false)
    },
    setPopupContent: function (content) {
      if (this._popup) {
        this._popup.setContent(content)
      }
      return this
    },
    getPopup: function () {return this._popup},
    _openPopup: function (e) {
      var layer = e.layer || e.target
      if (!this._popup) {return}
      if (!this._map) {return}
      stop(e)
      if (layer instanceof Path) {
        this.openPopup(e.layer || e.target, e.latlng)
        return
      }
      if (this._map.hasLayer(this._popup) && this._popup._source ===
        layer) {this.closePopup()} else {this.openPopup(layer, e.latlng)}
    },
    _movePopup: function (e) {this._popup.setLatLng(e.latlng)},
    _onKeyPress: function (e) {
      if (e.originalEvent.keyCode === 13) {this._openPopup(e)}
    }
  })
  var Tooltip = DivOverlay.extend({
    options: {
      pane: 'tooltipPane',
      offset: [0, 0],
      direction: 'auto',
      permanent: false,
      sticky: false,
      interactive: false,
      opacity: 0.9
    },
    onAdd: function (map) {
      DivOverlay.prototype.onAdd.call(this, map)
      this.setOpacity(this.options.opacity)
      map.fire('tooltipopen', { tooltip: this })
      if (this._source) {
        this._source.fire('tooltipopen', { tooltip: this }, true)
      }
    },
    onRemove: function (map) {
      DivOverlay.prototype.onRemove.call(this, map)
      map.fire('tooltipclose', { tooltip: this })
      if (this._source) {
        this._source.fire('tooltipclose', { tooltip: this }, true)
      }
    },
    getEvents: function () {
      var events = DivOverlay.prototype.getEvents.call(this)
      if (touch && !this.options.permanent) {events.preclick = this._close}
      return events
    },
    _close: function () {if (this._map) {this._map.closeTooltip(this)}},
    _initLayout: function () {
      var prefix = 'winfo-tooltip'
      var className = prefix + ' ' + (this.options.className || '') +
        ' winfo-zoom-' + (this._zoomAnimated ? 'animated' : 'hide')
      this._contentNode = this._container = create$1('div', className)
    },
    _updateLayout: function () {},
    _adjustPan: function () {},
    _setPosition: function (pos) {
      var map = this._map
      var container = this._container
      var centerPoint = map.latLngToContainerPoint(map.getCenter())
      var tooltipPoint = map.layerPointToContainerPoint(pos)
      var direction = this.options.direction
      var tooltipWidth = container.offsetWidth
      var tooltipHeight = container.offsetHeight
      var offset = toPoint(this.options.offset)
      var anchor = this._getAnchor()
      if (direction === 'top') {
        pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x,
          -tooltipHeight + offset.y + anchor.y, true))
      } else if (direction === 'bottom') {
        pos = pos.subtract(
          toPoint(tooltipWidth / 2 - offset.x, -offset.y, true))
      } else if (direction === 'center') {
        pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x,
          tooltipHeight / 2 - anchor.y + offset.y, true))
      } else if (direction === 'right' || direction === 'auto' &&
        tooltipPoint.x < centerPoint.x) {
        direction = 'right'
        pos = pos.add(toPoint(offset.x + anchor.x,
          anchor.y - tooltipHeight / 2 + offset.y, true))
      } else {
        direction = 'left'
        pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x,
          tooltipHeight / 2 - anchor.y - offset.y, true))
      }
      removeClass(container, 'winfo-tooltip-right')
      removeClass(container, 'winfo-tooltip-left')
      removeClass(container, 'winfo-tooltip-top')
      removeClass(container, 'winfo-tooltip-bottom')
      addClass(container, 'winfo-tooltip-' + direction)
      setPosition(container, pos)
    },
    _updatePosition: function () {
      var pos = this._map.latLngToLayerPoint(this._latlng)
      this._setPosition(pos)
    },
    setOpacity: function (opacity) {
      this.options.opacity = opacity
      if (this._container) {setOpacity(this._container, opacity)}
    },
    _animateZoom: function (e) {
      var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom,
        e.center)
      this._setPosition(pos)
    },
    _getAnchor: function () {
      return toPoint(
        this._source && this._source._getTooltipAnchor && !this.options.sticky
          ? this._source._getTooltipAnchor()
          : [0, 0])
    }
  })
  var tooltip = function (options, source) {
    return new Tooltip(options, source)
  }
  Map.include({
    openTooltip: function (tooltip, latlng, options) {
      if (!(tooltip instanceof Tooltip)) {
        tooltip = new Tooltip(options).setContent(tooltip)
      }
      if (latlng) {tooltip.setLatLng(latlng)}
      if (this.hasLayer(tooltip)) {return this}
      return this.addLayer(tooltip)
    },
    closeTooltip: function (tooltip) {
      if (tooltip) {this.removeLayer(tooltip)}
      return this
    }
  })
  Layer.include({
    bindTooltip: function (content, options) {
      if (content instanceof Tooltip) {
        setOptions(content, options)
        this._tooltip = content
        content._source = this
      } else {
        if (!this._tooltip || options) {
          this._tooltip = new Tooltip(options, this)
        }
        this._tooltip.setContent(content)
      }
      this._initTooltipInteractions()
      if (this._tooltip.options.permanent && this._map &&
        this._map.hasLayer(this)) {this.openTooltip()}
      return this
    },
    unbindTooltip: function () {
      if (this._tooltip) {
        this._initTooltipInteractions(true)
        this.closeTooltip()
        this._tooltip = null
      }
      return this
    },
    _initTooltipInteractions: function (remove$$1) {
      if (!remove$$1 && this._tooltipHandlersAdded) {return}
      var onOff = remove$$1 ? 'off' : 'on'
      var events = {
        remove: this.closeTooltip,
        move: this._moveTooltip
      }
      if (!this._tooltip.options.permanent) {
        events.mouseover = this._openTooltip
        events.mouseout = this.closeTooltip
        if (this._tooltip.options.sticky) {events.mousemove = this._moveTooltip}
        if (touch) {events.click = this._openTooltip}
      } else {events.add = this._openTooltip}
      this[onOff](events)
      this._tooltipHandlersAdded = !remove$$1
    },
    openTooltip: function (layer, latlng) {
      if (this._tooltip && this._map) {
        latlng = this._tooltip._prepareOpen(this, layer, latlng)
        this._map.openTooltip(this._tooltip, latlng)
        if (this._tooltip.options.interactive && this._tooltip._container) {
          addClass(this._tooltip._container, 'winfo-clickable')
          this.addInteractiveTarget(this._tooltip._container)
        }
      }
      return this
    },
    closeTooltip: function () {
      if (this._tooltip) {
        this._tooltip._close()
        if (this._tooltip.options.interactive && this._tooltip._container) {
          removeClass(this._tooltip._container, 'winfo-clickable')
          this.removeInteractiveTarget(this._tooltip._container)
        }
      }
      return this
    },
    toggleTooltip: function (target) {
      if (this._tooltip) {
        if (this._tooltip._map) {this.closeTooltip()} else {
          this.openTooltip(target)
        }
      }
      return this
    },
    isTooltipOpen: function () {return this._tooltip.isOpen()},
    setTooltipContent: function (content) {
      if (this._tooltip) {
        this._tooltip.setContent(content)
      }
      return this
    },
    getTooltip: function () {return this._tooltip},
    _openTooltip: function (e) {
      var layer = e.layer || e.target
      if (!this._tooltip || !this._map) {return}
      this.openTooltip(layer,
        this._tooltip.options.sticky ? e.latlng : undefined)
    },
    _moveTooltip: function (e) {
      var latlng = e.latlng
      var containerPoint
      var layerPoint
      if (this._tooltip.options.sticky && e.originalEvent) {
        containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent)
        layerPoint = this._map.containerPointToLayerPoint(containerPoint)
        latlng = this._map.layerPointToLatLng(layerPoint)
      }
      this._tooltip.setLatLng(latlng)
    }
  })
  var DivIcon = Icon.extend({
    options: {
      iconSize: [12, 12],
      html: false,
      bgPos: null,
      className: 'winfo-div-icon'
    },
    createIcon: function (oldIcon) {
      var div = (oldIcon && oldIcon.tagName === 'DIV')
        ? oldIcon
        : document.createElement('div')
      var options = this.options
      if (options.html instanceof Element) {
        empty(div)
        div.appendChild(options.html)
      } else {div.innerHTML = options.html !== false ? options.html : ''}
      if (options.bgPos) {
        var bgPos = toPoint(options.bgPos)
        div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'
      }
      this._setIconStyles(div, 'icon')
      return div
    },
    createShadow: function () {return null}
  })

  function divIcon (options) {return new DivIcon(options)}

  Icon.Default = IconDefault
  var GridLayer = Layer.extend({
    options: {
      tileSize: 256,
      opacity: 1,
      updateWhenIdle: mobile,
      updateWhenZooming: true,
      updateInterval: 200,
      zIndex: 1,
      bounds: null,
      minZoom: 0,
      maxZoom: undefined,
      maxNativeZoom: undefined,
      minNativeZoom: undefined,
      noWrap: false,
      pane: 'tilePane',
      className: '',
      keepBuffer: 2
    },
    initialize: function (options) {setOptions(this, options)},
    onAdd: function () {
      this._initContainer()
      this._levels = {}
      this._tiles = {}
      this._resetView()
      this._update()
    },
    beforeAdd: function (map) {map._addZoomLimit(this)},
    onRemove: function (map) {
      this._removeAllTiles()
      remove(this._container)
      map._removeZoomLimit(this)
      this._container = null
      this._tileZoom = undefined
    },
    bringToFront: function () {
      if (this._map) {
        toFront(this._container)
        this._setAutoZIndex(Math.max)
      }
      return this
    },
    bringToBack: function () {
      if (this._map) {
        toBack(this._container)
        this._setAutoZIndex(Math.min)
      }
      return this
    },
    getContainer: function () {return this._container},
    setOpacity: function (opacity) {
      this.options.opacity = opacity
      this._updateOpacity()
      return this
    },
    setZIndex: function (zIndex) {
      this.options.zIndex = zIndex
      this._updateZIndex()
      return this
    },
    isLoading: function () {return this._loading},
    redraw: function () {
      if (this._map) {
        this._removeAllTiles()
        this._update()
      }
      return this
    },
    getEvents: function () {
      var events = {
        viewprereset: this._invalidateAll,
        viewreset: this._resetView,
        zoom: this._resetView,
        moveend: this._onMoveEnd
      }
      if (!this.options.updateWhenIdle) {
        if (!this._onMove) {
          this._onMove = throttle(this._onMoveEnd, this.options.updateInterval,
            this)
        }
        events.move = this._onMove
      }
      if (this._zoomAnimated) {events.zoomanim = this._animateZoom}
      return events
    },
    createTile: function () {return document.createElement('div')},
    getTileSize: function () {
      var s = this.options.tileSize
      return s instanceof Point ? s : new Point(s, s)
    },
    _updateZIndex: function () {
      if (this._container && this.options.zIndex !== undefined &&
        this.options.zIndex !==
        null) {this._container.style.zIndex = this.options.zIndex}
    },
    _setAutoZIndex: function (compare) {
      var layers = this.getPane().children
      var edgeZIndex = -compare(-Infinity, Infinity)
      for (var i = 0, len = layers.length, zIndex; i < len; i++) {
        zIndex = layers[i].style.zIndex
        if (layers[i] !== this._container && zIndex) {
          edgeZIndex = compare(edgeZIndex, +zIndex)
        }
      }
      if (isFinite(edgeZIndex)) {
        this.options.zIndex = edgeZIndex + compare(-1, 1)
        this._updateZIndex()
      }
    },
    _updateOpacity: function () {
      if (!this._map) {return}
      if (ielt9) {return}
      setOpacity(this._container, this.options.opacity)
      var now = +new Date()
      var nextFrame = false
      var willPrune = false
      for (var key in this._tiles) {
        var tile = this._tiles[key]
        if (!tile.current || !tile.loaded) {continue}
        var fade = Math.min(1, (now - tile.loaded) / 200)
        setOpacity(tile.el, fade)
        if (fade < 1) {nextFrame = true} else {
          if (tile.active) {willPrune = true} else {
            this._onOpaqueTile(tile)
          }
          tile.active = true
        }
      }
      if (willPrune && !this._noPrune) {this._pruneTiles()}
      if (nextFrame) {
        cancelAnimFrame(this._fadeFrame)
        this._fadeFrame = requestAnimFrame(this._updateOpacity, this)
      }
    },
    _onOpaqueTile: falseFn,
    _initContainer: function () {
      if (this._container) {return}
      this._container = create$1('div',
        'winfo-layer ' + (this.options.className || ''))
      this._updateZIndex()
      if (this.options.opacity < 1) {this._updateOpacity()}
      this.getPane().appendChild(this._container)
    },
    _updateLevels: function () {
      var zoom = this._tileZoom
      var maxZoom = this.options.maxZoom
      if (zoom === undefined) {return undefined}
      for (var z in this._levels) {
        if (this._levels[z].el.children.length || z === zoom) {
          this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z)
          this._onUpdateLevel(z)
        } else {
          remove(this._levels[z].el)
          this._removeTilesAtZoom(z)
          this._onRemoveLevel(z)
          delete this._levels[z]
        }
      }
      var level = this._levels[zoom]
      var map = this._map
      if (!level) {
        level = this._levels[zoom] = {}
        level.el = create$1('div', 'winfo-tile-container winfo-zoom-animated',
          this._container)
        level.el.style.zIndex = maxZoom
        level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).
        round()
        level.zoom = zoom
        this._setZoomTransform(level, map.getCenter(), map.getZoom())
        falseFn(level.el.offsetWidth)
        this._onCreateLevel(level)
      }
      this._level = level
      return level
    },
    _onUpdateLevel: falseFn,
    _onRemoveLevel: falseFn,
    _onCreateLevel: falseFn,
    _pruneTiles: function () {
      if (!this._map) {return}
      var key, tile
      var zoom = this._map.getZoom()
      if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
        this._removeAllTiles()
        return
      }
      for (key in this._tiles) {
        tile = this._tiles[key]
        tile.retain = tile.current
      }
      for (key in this._tiles) {
        tile = this._tiles[key]
        if (tile.current && !tile.active) {
          var coords = tile.coords
          if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
            this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2)
          }
        }
      }
      for (key in this._tiles) {
        if (!this._tiles[key].retain) {
          this._removeTile(key)
        }
      }
    },
    _removeTilesAtZoom: function (zoom) {
      for (var key in this._tiles) {
        if (this._tiles[key].coords.z !== zoom) {continue}
        this._removeTile(key)
      }
    },
    _removeAllTiles: function () {
      for (var key in this._tiles) {
        this._removeTile(key)
      }
    },
    _invalidateAll: function () {
      for (var z in this._levels) {
        remove(this._levels[z].el)
        this._onRemoveLevel(z)
        delete this._levels[z]
      }
      this._removeAllTiles()
      this._tileZoom = undefined
    },
    _retainParent: function (x, y, z, minZoom) {
      var x2 = Math.floor(x / 2)
      var y2 = Math.floor(y / 2)
      var z2 = z - 1
      var coords2 = new Point(+x2, +y2)
      coords2.z = +z2
      var key = this._tileCoordsToKey(coords2)
      var tile = this._tiles[key]
      if (tile && tile.active) {
        tile.retain = true
        return true
      } else if (tile && tile.loaded) {tile.retain = true}
      if (z2 > minZoom) {return this._retainParent(x2, y2, z2, minZoom)}
      return false
    },
    _retainChildren: function (x, y, z, maxZoom) {
      for (var i = 2 * x; i < 2 * x + 2; i++) {
        for (var j = 2 * y; j < 2 * y + 2; j++) {
          var coords = new Point(i, j)
          coords.z = z + 1
          var key = this._tileCoordsToKey(coords)
          var tile = this._tiles[key]
          if (tile && tile.active) {
            tile.retain = true
            continue
          } else if (tile && tile.loaded) {tile.retain = true}
          if (z + 1 < maxZoom) {this._retainChildren(i, j, z + 1, maxZoom)}
        }
      }
    },
    _resetView: function (e) {
      var animating = e && (e.pinch || e.flyTo)
      this._setView(this._map.getCenter(), this._map.getZoom(), animating,
        animating)
    },
    _animateZoom: function (e) {
      this._setView(e.center, e.zoom, true, e.noUpdate)
    },
    _clampZoom: function (zoom) {
      var options = this.options
      if (undefined !== options.minNativeZoom && zoom <
        options.minNativeZoom) {return options.minNativeZoom}
      if (undefined !== options.maxNativeZoom && options.maxNativeZoom <
        zoom) {return options.maxNativeZoom}
      return zoom
    },
    _setView: function (center, zoom, noPrune, noUpdate) {
      var tileZoom = this._clampZoom(Math.round(zoom))
      if ((this.options.maxZoom !== undefined && tileZoom >
        this.options.maxZoom) ||
        (this.options.minZoom !== undefined && tileZoom <
          this.options.minZoom)) {tileZoom = undefined}
      var tileZoomChanged = this.options.updateWhenZooming &&
        (tileZoom !== this._tileZoom)
      if (!noUpdate || tileZoomChanged) {
        this._tileZoom = tileZoom
        if (this._abortLoading) {this._abortLoading()}
        this._updateLevels()
        this._resetGrid()
        if (tileZoom !== undefined) {this._update(center)}
        if (!noPrune) {this._pruneTiles()}
        this._noPrune = !!noPrune
      }
      this._setZoomTransforms(center, zoom)
    },
    _setZoomTransforms: function (center, zoom) {
      for (var i in this._levels) {
        this._setZoomTransform(this._levels[i], center, zoom)
      }
    },
    _setZoomTransform: function (level, center, zoom) {
      var scale = this._map.getZoomScale(zoom, level.zoom)
      var translate = level.origin.multiplyBy(scale).
      subtract(this._map._getNewPixelOrigin(center, zoom)).
      round()
      if (any3d) {setTransform(level.el, translate, scale)} else {
        setPosition(level.el, translate)
      }
    },
    _resetGrid: function () {
      var map = this._map
      var crs = map.options.crs
      var tileSize = this._tileSize = this.getTileSize()
      var tileZoom = this._tileZoom
      var bounds = this._map.getPixelWorldBounds(this._tileZoom)
      if (bounds) {this._globalTileRange = this._pxBoundsToTileRange(bounds)}
      this._wrapX = crs.wrapLng && !this.options.noWrap && [
        Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
        Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
      ]
      this._wrapY = crs.wrapLat && !this.options.noWrap && [
        Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
        Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
      ]
    },
    _onMoveEnd: function () {
      if (!this._map || this._map._animatingZoom) {return}
      this._update()
    },
    _getTiledPixelBounds: function (center) {
      var map = this._map
      var mapZoom = map._animatingZoom ? Math.max(map._animateToZoom,
        map.getZoom()) : map.getZoom()
      var scale = map.getZoomScale(mapZoom, this._tileZoom)
      var pixelCenter = map.project(center, this._tileZoom).floor()
      var halfSize = map.getSize().divideBy(scale * 2)
      return new Bounds(pixelCenter.subtract(halfSize),
        pixelCenter.add(halfSize))
    },
    _update: function (center) {
      var map = this._map
      if (!map) {return}
      var zoom = this._clampZoom(map.getZoom())
      if (center === undefined) {center = map.getCenter()}
      if (this._tileZoom === undefined) {return}
      var pixelBounds = this._getTiledPixelBounds(center)
      var tileRange = this._pxBoundsToTileRange(pixelBounds)
      var tileCenter = tileRange.getCenter()
      var queue = []
      var margin = this.options.keepBuffer
      var noPruneRange = new Bounds(
        tileRange.getBottomLeft().subtract([margin, -margin]),
        tileRange.getTopRight().add([margin, -margin]))
      if (!(isFinite(tileRange.min.x) && isFinite(tileRange.min.y) &&
        isFinite(tileRange.max.x) && isFinite(tileRange.max.y))) {
        throw new Error('Attempted to load an infinite number of tiles')
      }
      for (var key in this._tiles) {
        var c = this._tiles[key].coords
        if (c.z !== this._tileZoom || !noPruneRange.contains(
          new Point(c.x, c.y))) {this._tiles[key].current = false}
      }
      if (Math.abs(zoom - this._tileZoom) > 1) {
        this._setView(center, zoom)
        return
      }
      for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
        for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
          var coords = new Point(i, j)
          coords.z = this._tileZoom
          if (!this._isValidTile(coords)) {continue}
          var tile = this._tiles[this._tileCoordsToKey(coords)]
          if (tile) {tile.current = true} else {queue.push(coords)}
        }
      }
      queue.sort(function (a, b) {
        return a.distanceTo(tileCenter) - b.distanceTo(tileCenter)
      })
      if (queue.length !== 0) {
        if (!this._loading) {
          this._loading = true
          this.fire('loading')
        }
        var fragment = document.createDocumentFragment()
        for (i = 0; i < queue.length; i++) {this._addTile(queue[i], fragment)}
        this._level.el.appendChild(fragment)
      }
    },
    _isValidTile: function (coords) {
      var crs = this._map.options.crs
      if (!crs.infinite) {
        var bounds = this._globalTileRange
        if ((!crs.wrapLng &&
          (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
          (!crs.wrapLat && (coords.y < bounds.min.y || coords.y >
            bounds.max.y))) {return false}
      }
      if (!this.options.bounds) {return true}
      var tileBounds = this._tileCoordsToBounds(coords)
      return toLatLngBounds(this.options.bounds).overlaps(tileBounds)
    },
    _keyToBounds: function (key) {
      return this._tileCoordsToBounds(this._keyToTileCoords(key))
    },
    _tileCoordsToNwSe: function (coords) {
      var map = this._map
      var tileSize = this.getTileSize()
      var nwPoint = coords.scaleBy(tileSize)
      var sePoint = nwPoint.add(tileSize)
      var nw = map.unproject(nwPoint, coords.z)
      var se = map.unproject(sePoint, coords.z)
      return [nw, se]
    },
    _tileCoordsToBounds: function (coords) {
      var bp = this._tileCoordsToNwSe(coords)
      var bounds = new LatLngBounds(bp[0], bp[1])
      if (!this.options.noWrap) {bounds = this._map.wrapLatLngBounds(bounds)}
      return bounds
    },
    _tileCoordsToKey: function (coords) {
      return coords.x + ':' + coords.y + ':' + coords.z
    },
    _keyToTileCoords: function (key) {
      var k = key.split(':')
      var coords = new Point(+k[0], +k[1])
      coords.z = +k[2]
      return coords
    },
    _removeTile: function (key) {
      var tile = this._tiles[key]
      if (!tile) {return}
      remove(tile.el)
      delete this._tiles[key]
      this.fire('tileunload', {
        tile: tile.el,
        coords: this._keyToTileCoords(key)
      })
    },
    _initTile: function (tile) {
      addClass(tile, 'winfo-tile')
      var tileSize = this.getTileSize()
      tile.style.width = tileSize.x + 'px'
      tile.style.height = tileSize.y + 'px'
      tile.onselectstart = falseFn
      tile.onmousemove = falseFn
      if (ielt9 && this.options.opacity < 1) {
        setOpacity(tile, this.options.opacity)
      }
      if (android &&
        !android23) {tile.style.WebkitBackfaceVisibility = 'hidden'}
    },
    _addTile: function (coords, container) {
      var tilePos = this._getTilePos(coords)
      var key = this._tileCoordsToKey(coords)
      var tile = this.createTile(this._wrapCoords(coords),
        bind(this._tileReady, this, coords))
      this._initTile(tile)
      if (this.createTile.length < 2) {
        requestAnimFrame(bind(this._tileReady, this, coords, null, tile))
      }
      setPosition(tile, tilePos)
      this._tiles[key] = {
        el: tile,
        coords: coords,
        current: true
      }
      container.appendChild(tile)
      this.fire('tileloadstart', {
        tile: tile,
        coords: coords
      })
    },
    _tileReady: function (coords, err, tile) {
      if (err) {
        this.fire('tileerror', {
          error: err,
          tile: tile,
          coords: coords
        })
      }
      var key = this._tileCoordsToKey(coords)
      tile = this._tiles[key]
      if (!tile) {return}
      tile.loaded = +new Date()
      if (this._map._fadeAnimated) {
        setOpacity(tile.el, 0)
        cancelAnimFrame(this._fadeFrame)
        this._fadeFrame = requestAnimFrame(this._updateOpacity, this)
      } else {
        tile.active = true
        this._pruneTiles()
      }
      if (!err) {
        addClass(tile.el, 'winfo-tile-loaded')
        this.fire('tileload', {
          tile: tile.el,
          coords: coords
        })
      }
      if (this._noTilesToLoad()) {
        this._loading = false
        this.fire('load')
        if (ielt9 || !this._map._fadeAnimated) {
          requestAnimFrame(this._pruneTiles, this)
        } else {setTimeout(bind(this._pruneTiles, this), 250)}
      }
    },
    _getTilePos: function (coords) {
      return coords.scaleBy(this.getTileSize()).
      subtract(this._level.origin)
    },
    _wrapCoords: function (coords) {
      var newCoords = new Point(
        this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
        this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y)
      newCoords.z = coords.z
      return newCoords
    },
    _pxBoundsToTileRange: function (bounds) {
      var tileSize = this.getTileSize()
      return new Bounds(bounds.min.unscaleBy(tileSize).floor(),
        bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]))
    },
    _noTilesToLoad: function () {
      for (var key in this._tiles) {if (!this._tiles[key].loaded) {return false}}
      return true
    }
  })

  function gridLayer (options) {return new GridLayer(options)}

  var TileLayer = GridLayer.extend({
    options: {
      minZoom: 0,
      maxZoom: 18,
      subdomains: 'abc',
      errorTileUrl: '',
      zoomOffset: 0,
      tms: false,
      zoomReverse: false,
      detectRetina: false,
      crossOrigin: false
    },
    initialize: function (url, options) {
      this._url = url
      options = setOptions(this, options)
      if (options.detectRetina && retina && options.maxZoom > 0) {
        options.tileSize = Math.floor(options.tileSize / 2)
        if (!options.zoomReverse) {
          options.zoomOffset++
          options.maxZoom--
        } else {
          options.zoomOffset--
          options.minZoom++
        }
        options.minZoom = Math.max(0, options.minZoom)
      }
      if (typeof options.subdomains ===
        'string') {options.subdomains = options.subdomains.split('')}
      if (!android) {this.on('tileunload', this._onTileRemove)}
    },
    setUrl: function (url, noRedraw) {
      if (this._url === url && noRedraw === undefined) {noRedraw = true}
      this._url = url
      if (!noRedraw) {this.redraw()}
      return this
    },
    createTile: function (coords, done) {
      var tile = document.createElement('img')
      on(tile, 'load', bind(this._tileOnLoad, this, done, tile))
      on(tile, 'error', bind(this._tileOnError, this, done, tile))
      if (this.options.crossOrigin || this.options.crossOrigin === '') {
        tile.crossOrigin = this.options.crossOrigin === true
          ? ''
          : this.options.crossOrigin
      }
      tile.alt = ''
      tile.setAttribute('role', 'presentation')
      tile.src = this.getTileUrl(coords)
      return tile
    },
    getTileUrl: function (coords) {
      var data = {
        r: retina ? '@2x' : '',
        s: this._getSubdomain(coords),
        x: coords.x,
        y: coords.y,
        z: this._getZoomForUrl()
      }
      if (this._map && !this._map.options.crs.infinite) {
        var invertedY = this._globalTileRange.max.y - coords.y
        if (this.options.tms) {data['y'] = invertedY}
        data['-y'] = invertedY
      }
      return template(this._url, extend(data, this.options))
    },
    _tileOnLoad: function (done, tile) {
      if (ielt9) {
        setTimeout(bind(done, this, null, tile), 0)
      } else {done(null, tile)}
    },
    _tileOnError: function (done, tile, e) {
      var errorUrl = this.options.errorTileUrl
      if (errorUrl && tile.getAttribute('src') !==
        errorUrl) {tile.src = errorUrl}
      done(e, tile)
    },
    _onTileRemove: function (e) {e.tile.onload = null},
    _getZoomForUrl: function () {
      var zoom = this._tileZoom
      var maxZoom = this.options.maxZoom
      var zoomReverse = this.options.zoomReverse
      var zoomOffset = this.options.zoomOffset
      if (zoomReverse) {zoom = maxZoom - zoom}
      return zoom + zoomOffset
    },
    _getSubdomain: function (tilePoint) {
      var index = Math.abs(tilePoint.x + tilePoint.y) %
        this.options.subdomains.length
      return this.options.subdomains[index]
    },
    _abortLoading: function () {
      var i, tile
      for (i in this._tiles) {
        if (this._tiles[i].coords.z !== this._tileZoom) {
          tile = this._tiles[i].el
          tile.onload = falseFn
          tile.onerror = falseFn
          if (!tile.complete) {
            tile.src = emptyImageUrl
            remove(tile)
            delete this._tiles[i]
          }
        }
      }
    },
    _removeTile: function (key) {
      var tile = this._tiles[key]
      if (!tile) {return}
      if (!androidStock) {tile.el.setAttribute('src', emptyImageUrl)}
      return GridLayer.prototype._removeTile.call(this, key)
    },
    _tileReady: function (coords, err, tile) {
      if (!this._map ||
        (tile && tile.getAttribute('src') === emptyImageUrl)) {return}
      return GridLayer.prototype._tileReady.call(this, coords, err, tile)
    }
  })

  function tileLayer (url, options) {return new TileLayer(url, options)}

  var TileLayerWMS = TileLayer.extend({
    defaultWmsParams: {
      service: 'WMS',
      request: 'GetMap',
      layers: '',
      styles: '',
      format: 'image/jpeg',
      transparent: false,
      version: '1.1.1'
    },
    options: {
      crs: null,
      uppercase: false
    },
    initialize: function (url, options) {
      this._url = url
      var wmsParams = extend({}, this.defaultWmsParams)
      for (var i in options) {
        if (!(i in this.options)) {wmsParams[i] = options[i]}
      }
      options = setOptions(this, options)
      var realRetina = options.detectRetina && retina ? 2 : 1
      var tileSize = this.getTileSize()
      wmsParams.width = tileSize.x * realRetina
      wmsParams.height = tileSize.y * realRetina
      this.wmsParams = wmsParams
    },
    onAdd: function (map) {
      this._crs = this.options.crs || map.options.crs
      this._wmsVersion = parseFloat(this.wmsParams.version)
      var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'
      this.wmsParams[projectionKey] = this._crs.code
      TileLayer.prototype.onAdd.call(this, map)
    },
    getTileUrl: function (coords) {
      var tileBounds = this._tileCoordsToNwSe(coords)
      var crs = this._crs
      var bounds = toBounds(crs.project(tileBounds[0]),
        crs.project(tileBounds[1]))
      var min = bounds.min
      var max = bounds.max
      var bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? [
        min.y,
        min.x,
        max.y,
        max.x
      ] : [min.x, min.y, max.x, max.y]).join(',')
      var url = TileLayer.prototype.getTileUrl.call(this, coords)
      return url + getParamString(this.wmsParams, url, this.options.uppercase) +
        (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox
    },
    setParams: function (params, noRedraw) {
      extend(this.wmsParams, params)
      if (!noRedraw) {this.redraw()}
      return this
    }
  })

  function tileLayerWMS (url, options) {return new TileLayerWMS(url, options)}

  TileLayer.WMS = TileLayerWMS
  tileLayer.wms = tileLayerWMS
  var Renderer = Layer.extend({
    options: {
      padding: 0.1,
      tolerance: 0
    },
    initialize: function (options) {
      setOptions(this, options)
      stamp(this)
      this._layers = this._layers || {}
    },
    onAdd: function () {
      if (!this._container) {
        this._initContainer()
        if (this._zoomAnimated) {
          addClass(this._container, 'winfo-zoom-animated')
        }
      }
      this.getPane().appendChild(this._container)
      this._update()
      this.on('update', this._updatePaths, this)
    },
    onRemove: function () {
      this.off('update', this._updatePaths, this)
      this._destroyContainer()
    },
    getEvents: function () {
      var events = {
        viewreset: this._reset,
        zoom: this._onZoom,
        moveend: this._update,
        zoomend: this._onZoomEnd
      }
      if (this._zoomAnimated) {events.zoomanim = this._onAnimZoom}
      return events
    },
    _onAnimZoom: function (ev) {this._updateTransform(ev.center, ev.zoom)},
    _onZoom: function () {
      this._updateTransform(this._map.getCenter(), this._map.getZoom())
    },
    _updateTransform: function (center, zoom) {
      var scale = this._map.getZoomScale(zoom, this._zoom)
      var position = getPosition(this._container)
      var viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding)
      var currentCenterPoint = this._map.project(this._center, zoom)
      var destCenterPoint = this._map.project(center, zoom)
      var centerOffset = destCenterPoint.subtract(currentCenterPoint)
      var topLeftOffset = viewHalf.multiplyBy(-scale).
      add(position).
      add(viewHalf).
      subtract(centerOffset)
      if (any3d) {
        setTransform(this._container, topLeftOffset, scale)
      } else {setPosition(this._container, topLeftOffset)}
    },
    _reset: function () {
      this._update()
      this._updateTransform(this._center, this._zoom)
      for (var id in this._layers) {this._layers[id]._reset()}
    },
    _onZoomEnd: function () {for (var id in this._layers) {this._layers[id]._project()}},
    _updatePaths: function () {for (var id in this._layers) {this._layers[id]._update()}},
    _update: function () {
      var p = this.options.padding
      var size = this._map.getSize()
      var min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).
      round()
      this._bounds = new Bounds(min,
        min.add(size.multiplyBy(1 + p * 2)).round())
      this._center = this._map.getCenter()
      this._zoom = this._map.getZoom()
    }
  })
  var Canvas = Renderer.extend({
    getEvents: function () {
      var events = Renderer.prototype.getEvents.call(this)
      events.viewprereset = this._onViewPreReset
      return events
    },
    _onViewPreReset: function () {this._postponeUpdatePaths = true},
    onAdd: function () {
      Renderer.prototype.onAdd.call(this)
      this._draw()
    },
    _initContainer: function () {
      var container = this._container = document.createElement('canvas')
      on(container, 'mousemove', this._onMouseMove, this)
      on(container, 'click dblclick mousedown mouseup contextmenu',
        this._onClick, this)
      on(container, 'mouseout', this._handleMouseOut, this)
      this._ctx = container.getContext('2d')
    },
    _destroyContainer: function () {
      cancelAnimFrame(this._redrawRequest)
      delete this._ctx
      remove(this._container)
      off(this._container)
      delete this._container
    },
    _updatePaths: function () {
      if (this._postponeUpdatePaths) {return}
      var layer
      this._redrawBounds = null
      for (var id in this._layers) {
        layer = this._layers[id]
        layer._update()
      }
      this._redraw()
    },
    _update: function () {
      if (this._map._animatingZoom && this._bounds) {return}
      Renderer.prototype._update.call(this)
      var b = this._bounds
      var container = this._container
      var size = b.getSize()
      var m = retina ? 2 : 1
      setPosition(container, b.min)
      container.width = m * size.x
      container.height = m * size.y
      container.style.width = size.x + 'px'
      container.style.height = size.y + 'px'
      if (retina) {this._ctx.scale(2, 2)}
      this._ctx.translate(-b.min.x, -b.min.y)
      this.fire('update')
    },
    _reset: function () {
      Renderer.prototype._reset.call(this)
      if (this._postponeUpdatePaths) {
        this._postponeUpdatePaths = false
        this._updatePaths()
      }
    },
    _initPath: function (layer) {
      this._updateDashArray(layer)
      this._layers[stamp(layer)] = layer
      var order = layer._order = {
        layer: layer,
        prev: this._drawLast,
        next: null
      }
      if (this._drawLast) {this._drawLast.next = order}
      this._drawLast = order
      this._drawFirst = this._drawFirst || this._drawLast
    },
    _addPath: function (layer) {this._requestRedraw(layer)},
    _removePath: function (layer) {
      var order = layer._order
      var next = order.next
      var prev = order.prev
      if (next) {next.prev = prev} else {this._drawLast = prev}
      if (prev) {prev.next = next} else {this._drawFirst = next}
      delete layer._order
      delete this._layers[stamp(layer)]
      this._requestRedraw(layer)
    },
    _updatePath: function (layer) {
      this._extendRedrawBounds(layer)
      layer._project()
      layer._update()
      this._requestRedraw(layer)
    },
    _updateStyle: function (layer) {
      this._updateDashArray(layer)
      this._requestRedraw(layer)
    },
    _updateDashArray: function (layer) {
      if (typeof layer.options.dashArray === 'string') {
        var parts = layer.options.dashArray.split(/[, ]+/)
        var dashArray = []
        var dashValue
        var i
        for (i = 0; i < parts.length; i++) {
          dashValue = Number(parts[i])
          if (isNaN(dashValue)) {return}
          dashArray.push(dashValue)
        }
        layer.options._dashArray = dashArray
      } else {layer.options._dashArray = layer.options.dashArray}
    },
    _requestRedraw: function (layer) {
      if (!this._map) {return}
      this._extendRedrawBounds(layer)
      this._redrawRequest = this._redrawRequest ||
        requestAnimFrame(this._redraw, this)
    },
    _extendRedrawBounds: function (layer) {
      if (layer._pxBounds) {
        var padding = (layer.options.weight || 0) + 1
        this._redrawBounds = this._redrawBounds || new Bounds()
        this._redrawBounds.extend(
          layer._pxBounds.min.subtract([padding, padding]))
        this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]))
      }
    },
    _redraw: function () {
      this._redrawRequest = null
      if (this._redrawBounds) {
        this._redrawBounds.min._floor()
        this._redrawBounds.max._ceil()
      }
      this._clear()
      this._draw()
      this._redrawBounds = null
    },
    _clear: function () {
      var bounds = this._redrawBounds
      if (bounds) {
        var size = bounds.getSize()
        this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y)
      } else {
        this._ctx.clearRect(0, 0, this._container.width,
          this._container.height)
      }
    },
    _clearContent: function () {
      if (!this._ctx) return
      var canvas = this._ctx.canvas
      var offset = canvas._winfo_pos
      this._ctx.clearRect(offset.x, offset.y, canvas.width, canvas.width)
    },
    _draw: function () {
      var layer
      var bounds = this._redrawBounds
      this._ctx.save()
      if (bounds) {
        var size = bounds.getSize()
        this._ctx.beginPath()
        this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y)
        this._ctx.clip()
      }
      this._drawing = true
      for (var order = this._drawFirst; order; order = order.next) {
        layer = order.layer
        if (!bounds || (layer._pxBounds &&
          layer._pxBounds.intersects(bounds))) {layer._updatePath()}
      }
      this._drawing = false
      this._ctx.restore()
    },
    _updatePoly: function (layer, closed) {
      if (!this._drawing) {return}
      var i
      var j
      var len2
      var p
      var parts = layer._parts
      var len = parts.length
      var ctx = this._ctx
      if (!len) {return}
      ctx.beginPath()
      for (i = 0; i < len; i++) {
        for (j = 0, len2 = parts[i].length; j < len2; j++) {
          p = parts[i][j]
          ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y)
        }
        if (closed) {ctx.closePath()}
      }
      this._fillStroke(ctx, layer)
    },
    _updateCircle: function (layer) {
      if (!this._drawing || layer._empty()) {return}
      var p = layer._point
      var ctx = this._ctx
      var r = Math.max(Math.round(layer._radius), 1)
      var s = (Math.max(Math.round(layer._radiusY), 1) || r) / r
      if (s !== 1) {
        ctx.save()
        ctx.scale(1, s)
      }
      ctx.beginPath()
      ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false)
      if (s !== 1) {ctx.restore()}
      this._fillStroke(ctx, layer)
    },
    _fillStroke: function (ctx, layer) {
      var options = layer.options
      if (options.fill) {
        ctx.globalAlpha = options.fillOpacity
        ctx.fillStyle = options.fillColor || options.color
        ctx.fill(options.fillRule || 'evenodd')
      }
      if (options.stroke && options.weight !== 0) {
        if (ctx.setLineDash) {
          ctx.setLineDash(layer.options && layer.options._dashArray || [])
        }
        ctx.globalAlpha = options.opacity
        ctx.lineWidth = options.weight
        ctx.strokeStyle = options.color
        ctx.lineCap = options.lineCap
        ctx.lineJoin = options.lineJoin
        ctx.stroke()
      }
    },
    _onClick: function (e) {
      var point = this._map.mouseEventToLayerPoint(e)
      var layer
      var clickedLayer
      for (var order = this._drawFirst; order; order = order.next) {
        layer = order.layer
        if (layer.options.interactive && layer._containsPoint(point) &&
          !this._map._draggableMoved(layer)) {clickedLayer = layer}
      }
      if (clickedLayer) {
        fakeStop(e)
        this._fireEvent([clickedLayer], e)
      }
    },
    _onMouseMove: function (e) {
      if (!this._map || this._map.dragging.moving() ||
        this._map._animatingZoom) {return}
      var point = this._map.mouseEventToLayerPoint(e)
      this._handleMouseHover(e, point)
    },
    _handleMouseOut: function (e) {
      var layer = this._hoveredLayer
      if (layer) {
        removeClass(this._container, 'winfo-interactive')
        this._fireEvent([layer], e, 'mouseout')
        this._hoveredLayer = null
        this._mouseHoverThrottled = false
      }
    },
    _handleMouseHover: function (e, point) {
      if (this._mouseHoverThrottled) {return}
      var layer, candidateHoveredLayer
      for (var order = this._drawFirst; order; order = order.next) {
        layer = order.layer
        if (layer.options.interactive &&
          layer._containsPoint(point)) {candidateHoveredLayer = layer}
      }
      if (candidateHoveredLayer !== this._hoveredLayer) {
        this._handleMouseOut(e)
        if (candidateHoveredLayer) {
          addClass(this._container, 'winfo-interactive')
          this._fireEvent([candidateHoveredLayer], e, 'mouseover')
          this._hoveredLayer = candidateHoveredLayer
        }
      }
      if (this._hoveredLayer) {this._fireEvent([this._hoveredLayer], e)}
      this._mouseHoverThrottled = true
      setTimeout(
        Util.bind(function () {this._mouseHoverThrottled = false}, this), 32)
    },
    _fireEvent: function (layers, e, type) {
      this._map._fireDOMEvent(e, type || e.type, layers)
    },
    _bringToFront: function (layer) {
      var order = layer._order
      if (!order) {return}
      var next = order.next
      var prev = order.prev
      if (next) {next.prev = prev} else {return}
      if (prev) {prev.next = next} else if (next) {this._drawFirst = next}
      order.prev = this._drawLast
      this._drawLast.next = order
      order.next = null
      this._drawLast = order
      this._requestRedraw(layer)
    },
    _bringToBack: function (layer) {
      var order = layer._order
      if (!order) {return}
      var next = order.next
      var prev = order.prev
      if (prev) {prev.next = next} else {return}
      if (next) {next.prev = prev} else if (prev) {this._drawLast = prev}
      order.prev = null
      order.next = this._drawFirst
      this._drawFirst.prev = order
      this._drawFirst = order
      this._requestRedraw(layer)
    }
  })

  function canvas$1 (options) {return canvas ? new Canvas(options) : null}

  var vmlCreate = (function () {
    try {
      document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml')
      return function (name) {
        return document.createElement('<lvml:' + name + ' class="lvml">')
      }
    } catch (e) {
      return function (name) {
        return document.createElement(
          '<' + name +
          ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')
      }
    }
  })()
  var vmlMixin = {
    _initContainer: function () {
      this._container = create$1('div', 'winfo-vml-container')
    },
    _update: function () {
      if (this._map._animatingZoom) {return}
      Renderer.prototype._update.call(this)
      this.fire('update')
    },
    _initPath: function (layer) {
      var container = layer._container = vmlCreate('shape')
      addClass(container, 'winfo-vml-shape ' + (this.options.className || ''))
      container.coordsize = '1 1'
      layer._path = vmlCreate('path')
      container.appendChild(layer._path)
      this._updateStyle(layer)
      this._layers[stamp(layer)] = layer
    },
    _addPath: function (layer) {
      var container = layer._container
      this._container.appendChild(container)
      if (layer.options.interactive) {layer.addInteractiveTarget(container)}
    },
    _removePath: function (layer) {
      var container = layer._container
      remove(container)
      layer.removeInteractiveTarget(container)
      delete this._layers[stamp(layer)]
    },
    _updateStyle: function (layer) {
      var stroke = layer._stroke
      var fill = layer._fill
      var options = layer.options
      var container = layer._container
      container.stroked = !!options.stroke
      container.filled = !!options.fill
      if (options.stroke) {
        if (!stroke) {
          stroke = layer._stroke = vmlCreate('stroke')
        }
        container.appendChild(stroke)
        stroke.weight = options.weight + 'px'
        stroke.color = options.color
        stroke.opacity = options.opacity
        if (options.dashArray) {
          stroke.dashStyle = isArray(options.dashArray)
            ? options.dashArray.join(' ')
            : options.dashArray.replace(/( *, *)/g, ' ')
        } else {stroke.dashStyle = ''}
        stroke.endcap = options.lineCap.replace('butt', 'flat')
        stroke.joinstyle = options.lineJoin
      } else if (stroke) {
        container.removeChild(stroke)
        layer._stroke = null
      }
      if (options.fill) {
        if (!fill) {fill = layer._fill = vmlCreate('fill')}
        container.appendChild(fill)
        fill.color = options.fillColor || options.color
        fill.opacity = options.fillOpacity
      } else if (fill) {
        container.removeChild(fill)
        layer._fill = null
      }
    },
    _updateCircle: function (layer) {
      var p = layer._point.round()
      var r = Math.round(layer._radius)
      var r2 = Math.round(layer._radiusY || r)
      this._setPath(layer,
        layer._empty() ? 'M0 0' : 'AL ' + p.x + ',' + p.y + ' ' + r + ',' +
          r2 + ' 0,' + (65535 * 360))
    },
    _setPath: function (layer, path) {layer._path.v = path},
    _bringToFront: function (layer) {toFront(layer._container)},
    _bringToBack: function (layer) {toBack(layer._container)}
  }
  var create$2 = vml ? vmlCreate : svgCreate
  var SVG = Renderer.extend({
    getEvents: function () {
      var events = Renderer.prototype.getEvents.call(this)
      events.zoomstart = this._onZoomStart
      return events
    },
    _initContainer: function () {
      this._container = create$2('svg')
      this._container.setAttribute('pointer-events', 'none')
      this._rootGroup = create$2('g')
      this._container.appendChild(this._rootGroup)
    },
    _destroyContainer: function () {
      remove(this._container)
      off(this._container)
      delete this._container
      delete this._rootGroup
      delete this._svgSize
    },
    _onZoomStart: function () {this._update()},
    _update: function () {
      if (this._map._animatingZoom && this._bounds) {return}
      Renderer.prototype._update.call(this)
      var b = this._bounds
      var size = b.getSize()
      var container = this._container
      if (!this._svgSize || !this._svgSize.equals(size)) {
        this._svgSize = size
        container.setAttribute('width', size.x)
        container.setAttribute('height', size.y)
      }
      setPosition(container, b.min)
      container.setAttribute('viewBox',
        [b.min.x, b.min.y, size.x, size.y].join(' '))
      this.fire('update')
    },
    _initPath: function (layer) {
      var path = layer._path = create$2('path')
      if (layer.options.className) {addClass(path, layer.options.className)}
      if (layer.options.interactive) {addClass(path, 'winfo-interactive')}
      this._updateStyle(layer)
      this._layers[stamp(layer)] = layer
    },
    _addPath: function (layer) {
      if (!this._rootGroup) {this._initContainer()}
      this._rootGroup.appendChild(layer._path)
      layer.addInteractiveTarget(layer._path)
    },
    _removePath: function (layer) {
      remove(layer._path)
      layer.removeInteractiveTarget(layer._path)
      delete this._layers[stamp(layer)]
    },
    _updatePath: function (layer) {
      layer._project()
      layer._update()
    },
    _updateStyle: function (layer) {
      var path = layer._path
      var options = layer.options
      if (!path) {return}
      if (options.stroke) {
        path.setAttribute('stroke', options.color)
        path.setAttribute('stroke-opacity', options.opacity)
        path.setAttribute('stroke-width', options.weight)
        path.setAttribute('stroke-linecap', options.lineCap)
        path.setAttribute('stroke-linejoin', options.lineJoin)
        if (options.dashArray) {
          path.setAttribute('stroke-dasharray', options.dashArray)
        } else {path.removeAttribute('stroke-dasharray')}
        if (options.dashOffset) {
          path.setAttribute('stroke-dashoffset', options.dashOffset)
        } else {path.removeAttribute('stroke-dashoffset')}
      } else {path.setAttribute('stroke', 'none')}
      if (options.fill) {
        path.setAttribute('fill', options.fillColor || options.color)
        path.setAttribute('fill-opacity', options.fillOpacity)
        path.setAttribute('fill-rule', options.fillRule || 'evenodd')
      } else {path.setAttribute('fill', 'none')}
    },
    _updatePoly: function (layer, closed) {
      this._setPath(layer, pointsToPath(layer._parts, closed))
    },
    _updateCircle: function (layer) {
      var p = layer._point
      var r = Math.max(Math.round(layer._radius), 1)
      var r2 = Math.max(Math.round(layer._radiusY), 1) || r
      var arc = 'a' + r + ',' + r2 + ' 0 1,0 '
      var d = layer._empty() ? 'M0 0' : 'M' + (p.x - r) + ',' + p.y + arc +
        (r * 2) + ',0 ' + arc + (-r * 2) + ',0 '
      this._setPath(layer, d)
    },
    _setPath: function (layer, path) {layer._path.setAttribute('d', path)},
    _bringToFront: function (layer) {toFront(layer._path)},
    _bringToBack: function (layer) {toBack(layer._path)}
  })
  if (vml) {SVG.include(vmlMixin)}

  function svg$1 (options) {return svg || vml ? new SVG(options) : null}

  Map.include({
    getRenderer: function (layer) {
      var renderer = layer.options.renderer ||
        this._getPaneRenderer(layer.options.pane) || this.options.renderer ||
        this._renderer
      if (!renderer) {renderer = this._renderer = this._createRenderer()}
      if (!this.hasLayer(renderer)) {this.addLayer(renderer)}
      return renderer
    },
    _getPaneRenderer: function (name) {
      if (name === 'overlayPane' || name === undefined) {return false}
      var renderer = this._paneRenderers[name]
      if (renderer === undefined) {
        renderer = this._createRenderer({ pane: name })
        this._paneRenderers[name] = renderer
      }
      return renderer
    },
    _createRenderer: function (options) {
      return (this.options.preferCanvas && canvas$1(options)) || svg$1(options)
    }
  })
  var Rectangle = Polygon.extend({
    initialize: function (latLngBounds, options) {
      Polygon.prototype.initialize.call(this,
        this._boundsToLatLngs(latLngBounds), options)
    },
    setBounds: function (latLngBounds) {
      return this.setLatLngs(this._boundsToLatLngs(latLngBounds))
    },
    _boundsToLatLngs: function (latLngBounds) {
      latLngBounds = toLatLngBounds(latLngBounds)
      return [
        latLngBounds.getSouthWest(),
        latLngBounds.getNorthWest(),
        latLngBounds.getNorthEast(),
        latLngBounds.getSouthEast()
      ]
    }
  })

  function rectangle (latLngBounds, options) {
    return new Rectangle(latLngBounds, options)
  }

  SVG.create = create$2
  SVG.pointsToPath = pointsToPath
  GeoJSON.geometryToLayer = geometryToLayer
  GeoJSON.coordsToLatLng = coordsToLatLng
  GeoJSON.coordsToLatLngs = coordsToLatLngs
  GeoJSON.latLngToCoords = latLngToCoords
  GeoJSON.latLngsToCoords = latLngsToCoords
  GeoJSON.getFeature = getFeature
  GeoJSON.asFeature = asFeature
  Map.mergeOptions({ boxZoom: true })
  var BoxZoom = Handler.extend({
    initialize: function (map) {
      this._map = map
      this._container = map._container
      this._pane = map._panes.overlayPane
      this._resetStateTimeout = 0
      map.on('unload', this._destroy, this)
    },
    addHooks: function () {
      on(this._container, 'mousedown', this._onMouseDown, this)
    },
    removeHooks: function () {
      off(this._container, 'mousedown', this._onMouseDown, this)
    },
    moved: function () {return this._moved},
    _destroy: function () {
      remove(this._pane)
      delete this._pane
    },
    _resetState: function () {
      this._resetStateTimeout = 0
      this._moved = false
    },
    _clearDeferredResetState: function () {
      if (this._resetStateTimeout !== 0) {
        clearTimeout(this._resetStateTimeout)
        this._resetStateTimeout = 0
      }
    },
    _onMouseDown: function (e) {
      if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) {return false}
      this._clearDeferredResetState()
      this._resetState()
      disableTextSelection()
      disableImageDrag()
      this._startPoint = this._map.mouseEventToContainerPoint(e)
      on(document, {
        contextmenu: stop,
        mousemove: this._onMouseMove,
        mouseup: this._onMouseUp,
        keydown: this._onKeyDown
      }, this)
    },
    _onMouseMove: function (e) {
      if (!this._moved) {
        this._moved = true
        this._box = create$1('div', 'winfo-zoom-box', this._container)
        addClass(this._container, 'winfo-crosshair')
        this._map.fire('boxzoomstart')
      }
      this._point = this._map.mouseEventToContainerPoint(e)
      var bounds = new Bounds(this._point, this._startPoint)
      var size = bounds.getSize()
      setPosition(this._box, bounds.min)
      this._box.style.width = size.x + 'px'
      this._box.style.height = size.y + 'px'
    },
    _finish: function () {
      if (this._moved) {
        remove(this._box)
        removeClass(this._container, 'winfo-crosshair')
      }
      enableTextSelection()
      enableImageDrag()
      off(document, {
        contextmenu: stop,
        mousemove: this._onMouseMove,
        mouseup: this._onMouseUp,
        keydown: this._onKeyDown
      }, this)
    },
    _onMouseUp: function (e) {
      if ((e.which !== 1) && (e.button !== 1)) {return}
      this._finish()
      if (!this._moved) {return}
      this._clearDeferredResetState()
      this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0)
      var bounds = new LatLngBounds(
        this._map.containerPointToLatLng(this._startPoint),
        this._map.containerPointToLatLng(this._point))
      this._map.fitBounds(bounds).fire('boxzoomend', { boxZoomBounds: bounds })
    },
    _onKeyDown: function (e) {if (e.keyCode === 27) {this._finish()}}
  })
  Map.addInitHook('addHandler', 'boxZoom', BoxZoom)
  Map.mergeOptions({ doubleClickZoom: true })
  var DoubleClickZoom = Handler.extend({
    addHooks: function () {
      this._map.on('dblclick', this._onDoubleClick, this)
    },
    removeHooks: function () {
      this._map.off('dblclick', this._onDoubleClick, this)
    },
    _onDoubleClick: function (e) {
      var map = this._map
      var oldZoom = map.getZoom()
      var delta = map.options.zoomDelta
      var zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta
      if (map.options.doubleClickZoom === 'center') {
        map.setZoom(zoom)
      } else {map.setZoomAround(e.containerPoint, zoom)}
    }
  })
  Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom)
  Map.mergeOptions({
    dragging: true,
    inertia: !android23,
    inertiaDeceleration: 3400,
    inertiaMaxSpeed: Infinity,
    easeLinearity: 0.2,
    worldCopyJump: false,
    maxBoundsViscosity: 0.0
  })
  var Drag = Handler.extend({
    addHooks: function () {
      if (!this._draggable) {
        var map = this._map
        this._draggable = new Draggable(map._mapPane, map._container)
        this._draggable.on({
          dragstart: this._onDragStart,
          drag: this._onDrag,
          dragend: this._onDragEnd
        }, this)
        this._draggable.on('predrag', this._onPreDragLimit, this)
        if (map.options.worldCopyJump) {
          this._draggable.on('predrag', this._onPreDragWrap, this)
          map.on('zoomend', this._onZoomEnd, this)
          map.whenReady(this._onZoomEnd, this)
        }
      }
      addClass(this._map._container, 'winfo-grab winfo-touch-drag')
      this._draggable.enable()
      this._positions = []
      this._times = []
    },
    removeHooks: function () {
      removeClass(this._map._container, 'winfo-grab')
      removeClass(this._map._container, 'winfo-touch-drag')
      this._draggable.disable()
    },
    moved: function () {return this._draggable && this._draggable._moved},
    moving: function () {return this._draggable && this._draggable._moving},
    _onDragStart: function () {
      var map = this._map
      map._stop()
      if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
        var bounds = toLatLngBounds(this._map.options.maxBounds)
        this._offsetLimit = toBounds(
          this._map.latLngToContainerPoint(bounds.getNorthWest()).
          multiplyBy(-1),
          this._map.latLngToContainerPoint(bounds.getSouthEast()).
          multiplyBy(-1).
          add(this._map.getSize()))
        this._viscosity = Math.min(1.0,
          Math.max(0.0, this._map.options.maxBoundsViscosity))
      } else {this._offsetLimit = null}
      map.fire('movestart').fire('dragstart')
      if (map.options.inertia) {
        this._positions = []
        this._times = []
      }
    },
    _onDrag: function (e) {
      if (this._map.options.inertia) {
        var time = this._lastTime = +new Date()
        var pos = this._lastPos = this._draggable._absPos ||
          this._draggable._newPos
        this._positions.push(pos)
        this._times.push(time)
        this._prunePositions(time)
      }
      this._map.fire('move', e).fire('drag', e)
    },
    _prunePositions: function (time) {
      while (this._positions.length > 1 && time - this._times[0] > 50) {
        this._positions.shift()
        this._times.shift()
      }
    },
    _onZoomEnd: function () {
      var pxCenter = this._map.getSize().divideBy(2)
      var pxWorldCenter = this._map.latLngToLayerPoint([0, 0])
      this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x
      this._worldWidth = this._map.getPixelWorldBounds().getSize().x
    },
    _viscousLimit: function (value, threshold) {
      return value - (value - threshold) * this._viscosity
    },
    _onPreDragLimit: function () {
      if (!this._viscosity || !this._offsetLimit) {return}
      var offset = this._draggable._newPos.subtract(this._draggable._startPos)
      var limit = this._offsetLimit
      if (offset.x < limit.min.x) {
        offset.x = this._viscousLimit(offset.x, limit.min.x)
      }
      if (offset.y < limit.min.y) {
        offset.y = this._viscousLimit(offset.y, limit.min.y)
      }
      if (offset.x > limit.max.x) {
        offset.x = this._viscousLimit(offset.x, limit.max.x)
      }
      if (offset.y > limit.max.y) {
        offset.y = this._viscousLimit(offset.y, limit.max.y)
      }
      this._draggable._newPos = this._draggable._startPos.add(offset)
    },
    _onPreDragWrap: function () {
      var worldWidth = this._worldWidth
      var halfWidth = Math.round(worldWidth / 2)
      var dx = this._initialWorldOffset
      var x = this._draggable._newPos.x
      var newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx
      var newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx
      var newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2
      this._draggable._absPos = this._draggable._newPos.clone()
      this._draggable._newPos.x = newX
    },
    _onDragEnd: function (e) {
      var map = this._map
      var options = map.options
      var noInertia = !options.inertia || this._times.length < 2
      map.fire('dragend', e)
      if (noInertia) {map.fire('moveend')} else {
        this._prunePositions(+new Date())
        var direction = this._lastPos.subtract(this._positions[0])
        var duration = (this._lastTime - this._times[0]) / 1000
        var ease = options.easeLinearity
        var speedVector = direction.multiplyBy(ease / duration)
        var speed = speedVector.distanceTo([0, 0])
        var limitedSpeed = Math.min(options.inertiaMaxSpeed, speed)
        var limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed)
        var decelerationDuration = limitedSpeed /
          (options.inertiaDeceleration * ease)
        var offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).
        round()
        if (!offset.x && !offset.y) {
          map.fire('moveend')
        } else {
          offset = map._limitOffset(offset, map.options.maxBounds)
          requestAnimFrame(function () {
            map.panBy(offset, {
              duration: decelerationDuration,
              easeLinearity: ease,
              noMoveStart: true,
              animate: true
            })
          })
        }
      }
    }
  })
  Map.addInitHook('addHandler', 'dragging', Drag)
  Map.mergeOptions({
    keyboard: true,
    keyboardPanDelta: 80
  })
  var Keyboard = Handler.extend({
    keyCodes: {
      left: [37],
      right: [39],
      down: [40],
      up: [38],
      zoomIn: [187, 107, 61, 171],
      zoomOut: [189, 109, 54, 173]
    },
    initialize: function (map) {
      this._map = map
      this._setPanDelta(map.options.keyboardPanDelta)
      this._setZoomDelta(map.options.zoomDelta)
    },
    addHooks: function () {
      var container = this._map._container
      if (container.tabIndex <= 0) {container.tabIndex = '0'}
      on(container, {
        focus: this._onFocus,
        blur: this._onBlur,
        mousedown: this._onMouseDown
      }, this)
      this._map.on({
        focus: this._addHooks,
        blur: this._removeHooks
      }, this)
    },
    removeHooks: function () {
      this._removeHooks()
      off(this._map._container, {
        focus: this._onFocus,
        blur: this._onBlur,
        mousedown: this._onMouseDown
      }, this)
      this._map.off({
        focus: this._addHooks,
        blur: this._removeHooks
      }, this)
    },
    _onMouseDown: function () {
      if (this._focused) {return}
      var body = document.body
      var docEl = document.documentElement
      var top = body.scrollTop || docEl.scrollTop
      var left = body.scrollLeft || docEl.scrollLeft
      this._map._container.focus()
      window.scrollTo(left, top)
    },
    _onFocus: function () {
      this._focused = true
      this._map.fire('focus')
    },
    _onBlur: function () {
      this._focused = false
      this._map.fire('blur')
    },
    _setPanDelta: function (panDelta) {
      var keys = this._panKeys = {}
      var codes = this.keyCodes
      var i
      var len
      for (i = 0, len = codes.left.length; i <
      len; i++) {keys[codes.left[i]] = [-1 * panDelta, 0]}
      for (i = 0, len = codes.right.length; i <
      len; i++) {keys[codes.right[i]] = [panDelta, 0]}
      for (i = 0, len = codes.down.length; i <
      len; i++) {keys[codes.down[i]] = [0, panDelta]}
      for (i = 0, len = codes.up.length; i < len; i++) {
        keys[codes.up[i]] = [
          0,
          -1 * panDelta
        ]
      }
    },
    _setZoomDelta: function (zoomDelta) {
      var keys = this._zoomKeys = {}
      var codes = this.keyCodes
      var i
      var len
      for (i = 0, len = codes.zoomIn.length; i <
      len; i++) {keys[codes.zoomIn[i]] = zoomDelta}
      for (i = 0, len = codes.zoomOut.length; i <
      len; i++) {keys[codes.zoomOut[i]] = -zoomDelta}
    },
    _addHooks: function () {on(document, 'keydown', this._onKeyDown, this)},
    _removeHooks: function () {
      off(document, 'keydown', this._onKeyDown, this)
    },
    _onKeyDown: function (e) {
      if (e.altKey || e.ctrlKey || e.metaKey) {return}
      var key = e.keyCode
      var map = this._map
      var offset
      if (key in this._panKeys) {
        if (!map._panAnim || !map._panAnim._inProgress) {
          offset = this._panKeys[key]
          if (e.shiftKey) {offset = toPoint(offset).multiplyBy(3)}
          map.panBy(offset)
          if (map.options.maxBounds) {
            map.panInsideBounds(map.options.maxBounds)
          }
        }
      } else if (key in this._zoomKeys) {
        map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key])
      } else if (key === 27 && map._popup &&
        map._popup.options.closeOnEscapeKey) {map.closePopup()} else {return}
      stop(e)
    }
  })
  Map.addInitHook('addHandler', 'keyboard', Keyboard)
  Map.mergeOptions({
    scrollWheelZoom: true,
    wheelDebounceTime: 40,
    wheelPxPerZoomLevel: 60
  })
  var ScrollWheelZoom = Handler.extend({
    addHooks: function () {
      on(this._map._container, 'mousewheel', this._onWheelScroll, this)
      this._delta = 0
    },
    removeHooks: function () {
      off(this._map._container, 'mousewheel', this._onWheelScroll, this)
    },
    _onWheelScroll: function (e) {
      var delta = getWheelDelta(e)
      var debounce = this._map.options.wheelDebounceTime
      this._delta += delta
      this._lastMousePos = this._map.mouseEventToContainerPoint(e)
      if (!this._startTime) {this._startTime = +new Date()}
      var left = Math.max(debounce - (+new Date() - this._startTime), 0)
      clearTimeout(this._timer)
      this._timer = setTimeout(bind(this._performZoom, this), left)
      stop(e)
    },
    _performZoom: function () {
      var map = this._map
      var zoom = map.getZoom()
      var snap = this._map.options.zoomSnap || 0
      map._stop()
      var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4)
      var d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2
      var d4 = snap ? Math.ceil(d3 / snap) * snap : d3
      var delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom
      this._delta = 0
      this._startTime = null
      if (!delta) {return}
      if (map.options.scrollWheelZoom === 'center') {
        map.setZoom(zoom + delta)
      } else {map.setZoomAround(this._lastMousePos, zoom + delta)}
    }
  })
  Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom)
  Map.mergeOptions({
    tap: true,
    tapTolerance: 15
  })
  var Tap = Handler.extend({
    addHooks: function () {
      on(this._map._container, 'touchstart', this._onDown, this)
    },
    removeHooks: function () {
      off(this._map._container, 'touchstart', this._onDown, this)
    },
    _onDown: function (e) {
      if (!e.touches) {return}
      preventDefault(e)
      this._fireClick = true
      if (e.touches.length > 1) {
        this._fireClick = false
        clearTimeout(this._holdTimeout)
        return
      }
      var first = e.touches[0]
      var el = first.target
      this._startPos = this._newPos = new Point(first.clientX, first.clientY)
      if (el.tagName && el.tagName.toLowerCase() === 'a') {
        addClass(el, 'winfo-active')
      }
      this._holdTimeout = setTimeout(bind(function () {
        if (this._isTapValid()) {
          this._fireClick = false
          this._onUp()
          this._simulateEvent('contextmenu', first)
        }
      }, this), 1000)
      this._simulateEvent('mousedown', first)
      on(document, {
        touchmove: this._onMove,
        touchend: this._onUp
      }, this)
    },
    _onUp: function (e) {
      clearTimeout(this._holdTimeout)
      off(document, {
        touchmove: this._onMove,
        touchend: this._onUp
      }, this)
      if (this._fireClick && e && e.changedTouches) {
        var first = e.changedTouches[0]
        var el = first.target
        if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
          removeClass(el, 'winfo-active')
        }
        this._simulateEvent('mouseup', first)
        if (this._isTapValid()) {this._simulateEvent('click', first)}
      }
    },
    _isTapValid: function () {
      return this._newPos.distanceTo(this._startPos) <=
        this._map.options.tapTolerance
    },
    _onMove: function (e) {
      var first = e.touches[0]
      this._newPos = new Point(first.clientX, first.clientY)
      this._simulateEvent('mousemove', first)
    },
    _simulateEvent: function (type, e) {
      var simulatedEvent = document.createEvent('MouseEvents')
      simulatedEvent._simulated = true
      e.target._simulatedClick = true
      simulatedEvent.initMouseEvent(type, true, true, window, 1, e.screenX,
        e.screenY, e.clientX, e.clientY, false, false, false, false, 0, null)
      e.target.dispatchEvent(simulatedEvent)
    }
  })
  if (touch && !pointer) {Map.addInitHook('addHandler', 'tap', Tap)}
  Map.mergeOptions({
    touchZoom: touch && !android23,
    bounceAtZoomLimits: true
  })
  var TouchZoom = Handler.extend({
    addHooks: function () {
      addClass(this._map._container, 'winfo-touch-zoom')
      on(this._map._container, 'touchstart', this._onTouchStart, this)
    },
    removeHooks: function () {
      removeClass(this._map._container, 'winfo-touch-zoom')
      off(this._map._container, 'touchstart', this._onTouchStart, this)
    },
    _onTouchStart: function (e) {
      var map = this._map
      if (!e.touches || e.touches.length !== 2 || map._animatingZoom ||
        this._zooming) {return}
      var p1 = map.mouseEventToContainerPoint(e.touches[0])
      var p2 = map.mouseEventToContainerPoint(e.touches[1])
      this._centerPoint = map.getSize()._divideBy(2)
      this._startLatLng = map.containerPointToLatLng(this._centerPoint)
      if (map.options.touchZoom !== 'center') {
        this._pinchStartLatLng = map.containerPointToLatLng(
          p1.add(p2)._divideBy(2))
      }
      this._startDist = p1.distanceTo(p2)
      this._startZoom = map.getZoom()
      this._moved = false
      this._zooming = true
      map._stop()
      on(document, 'touchmove', this._onTouchMove, this)
      on(document, 'touchend', this._onTouchEnd, this)
      preventDefault(e)
    },
    _onTouchMove: function (e) {
      if (!e.touches || e.touches.length !== 2 || !this._zooming) {return}
      var map = this._map
      var p1 = map.mouseEventToContainerPoint(e.touches[0])
      var p2 = map.mouseEventToContainerPoint(e.touches[1])
      var scale = p1.distanceTo(p2) / this._startDist
      this._zoom = map.getScaleZoom(scale, this._startZoom)
      if (!map.options.bounceAtZoomLimits &&
        ((this._zoom < map.getMinZoom() && scale < 1) ||
          (this._zoom > map.getMaxZoom() && scale >
            1))) {this._zoom = map._limitZoom(this._zoom)}
      if (map.options.touchZoom === 'center') {
        this._center = this._startLatLng
        if (scale === 1) {return}
      } else {
        var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint)
        if (scale === 1 && delta.x === 0 && delta.y === 0) {return}
        this._center = map.unproject(
          map.project(this._pinchStartLatLng, this._zoom).subtract(delta),
          this._zoom)
      }
      if (!this._moved) {
        map._moveStart(true, false)
        this._moved = true
      }
      cancelAnimFrame(this._animRequest)
      var moveFn = bind(map._move, map, this._center, this._zoom, {
        pinch: true,
        round: false
      })
      this._animRequest = requestAnimFrame(moveFn, this, true)
      preventDefault(e)
    },
    _onTouchEnd: function () {
      if (!this._moved || !this._zooming) {
        this._zooming = false
        return
      }
      this._zooming = false
      cancelAnimFrame(this._animRequest)
      off(document, 'touchmove', this._onTouchMove)
      off(document, 'touchend', this._onTouchEnd)
      if (this._map.options.zoomAnimation) {
        this._map._animateZoom(this._center, this._map._limitZoom(this._zoom),
          true, this._map.options.zoomSnap)
      } else {
        this._map._resetView(this._center, this._map._limitZoom(this._zoom))
      }
    }
  })
  Map.addInitHook('addHandler', 'touchZoom', TouchZoom)
  Map.BoxZoom = BoxZoom
  Map.DoubleClickZoom = DoubleClickZoom
  Map.Drag = Drag
  Map.Keyboard = Keyboard
  Map.ScrollWheelZoom = ScrollWheelZoom
  Map.Tap = Tap
  Map.TouchZoom = TouchZoom
  Object.freeze = freeze

  class CanvasLayer {
    constructor () {}

    setOptions (options = {}) {
      extendOptions(this.options, options)
    }

    add (map, { zIndex = 111, className = '' }) {
      this._map = map
      this._canvas = new Canvas().addTo(this._map)
      this._canvas._container.style.zIndex = zIndex
      className && DomUtil.addClass(this._canvas._container, className)
      this._eventBind('on')
    }

    _eventBind (type) {
      this.reDraw && this._canvas[type]('update', this.reDraw, this)
      Browser.mobile ||
      this._onMouseMove && this._map[type]('mousemove', this._onMouseMove, this)
      this._onMouseClick && this._map[type]('click', this._onMouseClick, this)
      this._onMoveEnd && this._map[type]('moveend', this._onMoveEnd, this)
    }

    remove () {
      this._eventBind('off')
      this._canvas.remove()
      this._canvas = null
      return null
    }

    setCursor (cursor = 'default') {
      document.getElementsByClassName('cursorLayer')[0].style.cursor = cursor
    }
  }

  var LayerType = Control.extend({
    options: {
      layerType: ['HaiTu', 'ArcGis', 'WeiXin', 'DiTu'],
      HaiTu: {
        url: 'http://www.ais.msa.gov.cn/MapService?service=wmts&request=gettile&tilematrixset=advsearoad&tilematrix={z}&tilecol={x}&tilerow={y}&format=image/png&layer=default&style=default&version=1.0.0',
        layer: '',
        image: '',
        options: {},
        name: '海图'
      },
      ArcGis: {
        url: 'http://pub.winfo.cc:2001/MapService.ashx?REQUEST=GetMap&SERVICE=CacheMap&Y={y}&X={x}&LEVEL={z}&LAYERS=SouthChinaENCTile',
        layer: '',
        image: '',
        options: {},
        name: 'ArcGis'
      },
      WeiXin: {
        url: `https://t4.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${TDTTK}`,
        Tagging: true,
        taggingUrl: `https://t1.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${TDTTK}`,
        taggingLayer: '',
        layer: '',
        image: '',
        options: {},
        name: '卫星'
      },
      DiTu: {
        url: `https://t4.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${TDTTK}`,
        Tagging: true,
        taggingUrl: `https://t6.tianditu.gov.cn/cta_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${TDTTK}`,
        taggingLayer: '',
        layer: '',
        image: '',
        options: {},
        name: '地图'
      },
      toggleCallback: false,
      isCollapsed: true,
      offsetCollapsed: 10,
      className: 'auto-layertype-style',
      hiddenDomOpacity: 1
    },
    _currentLayer: '',
    _layerIcon: ['HaiTu', 'ArcGis', 'WeiXin', 'DiTu'],
    _layerItemDomIndex: [],
    initialize: function (options) {
      options || (options = {})
      extendOptions(this.options, options)
      options.layerType &&
      (this._layerIcon = this.options.layerType = options.layerType)
      this.initLayer()
      return this
    },
    addTo: function (map) {
      this._map = map
      Control.prototype.addTo.call(this, this._map)
      let layer = this.getDefalutLayer()
      let layerData = this.options[layer]
      if (!this._map.hasLayer(layerData.layer)) {this.addNewLayer(layer)}
      return this
    },
    getDefalutLayer: function () {
      var layerKey = this.options.layerType[0]
      this._currentLayer = layerKey
      return layerKey
    },
    initLayer: function () {
      for (var i = 0; i < this.options.layerType.length; i++) {
        var layerKey = this.options.layerType[i]
        var layerConfig = this.options[layerKey]
        let options = extendOptions({
          id: 'layer' + layerKey,
          attribution: layerConfig.name
        }, this.options[layerKey].options)
        this.options[layerKey].layer = new TileLayer(layerConfig.url, options)
        if (layerConfig.Tagging) {
          this.options[layerKey].taggingLayer = new TileLayer(
            layerConfig.taggingUrl, { id: 'layerTagging' + layerKey })
        }
      }
    },
    addNewLayer: function (layerName) {
      var layer = this.options[layerName]
      this._map.addLayer(layer.layer)
      layer.Tagging && this._map.addLayer(layer.taggingLayer)
    },
    removeCurrentLayer: function () {
      var layer = this.options[this._currentLayer]
      this._map.removeLayer(layer.layer)
      layer.taggingLayer && this._map.removeLayer(layer.taggingLayer)
    },
    _createImage: function (key) {
      var image = DomUtil.create('img', 'winfo-control-LayerType-item-image')
      image.src = this.options[key].image
      return image
    },
    _createNameLabel: function (key) {
      var label = DomUtil.create('div', 'winfo-control-LayerType-item-label')
      label.innerHTML = this.options[key].name
      return label
    },
    onAdd: function () {
      this._createHTML()
      this._collapse()
      return this._container
    },
    _createHTML: function () {
      this._container = DomUtil.create('div',
        'winfo-control-LayerType' + ' ' + this.options.className)
      this.options.isCollapsed && this.initDomEvent()
      this._createLayerItem()
    },
    initDomEvent: function () {
      var that = this
      DomEvent.on(this._container, 'mouseover', function (e) {
        let box = document.getElementsByClassName('winfo-control-LayerType')[0]
        DomUtil.addClass(box, 'active')
        var items = document.getElementsByClassName(
          'winfo-control-LayerType-item')
        for (var i = 0; i < items.length; i++) {that.setExpandDom(items[i])}
      }, this._container)
      DomEvent.on(this._container, 'mouseout', function (e) {
        let box = document.getElementsByClassName('winfo-control-LayerType')[0]
        DomUtil.removeClass(box, 'active')
        if (that._leavelControl(e)) {
          var items = document.getElementsByClassName(
            'winfo-control-LayerType-item')
          that.pushSelectLayerTypeToBefore(items)
          var itemsIndex = items.length - 1
          for (var i = 0; i < items.length; i++) {
            that.setCollapseDom(items[i])
            itemsIndex--
          }
        }
      })
      DomEvent.on(this._container, 'touchstart', function (e) {
        var items = document.getElementsByClassName(
          'winfo-control-LayerType-item')
        for (var i = 0; i < items.length; i++) {that.setExpandDom(items[i])}
      })
    },
    pushSelectLayerTypeToBefore: function (items) {
      var oldIndex = this._layerItemDomIndex.indexOf(this._currentLayer)
      this._layerItemDomIndex.splice(oldIndex, 1)
      this._layerItemDomIndex.push(this._currentLayer)
    },
    _leavelControl: function (e) {
      var toElement = e.toElement
      return toElement &&
        (!DomUtil.hasClass(toElement, 'winfo-control-LayerType')) &&
        (!DomUtil.hasClass(toElement,
          'winfo-control-LayerType-item-image')) &&
        (!DomUtil.hasClass(toElement,
          'winfo-control-LayerType-item-label')) &&
        (!DomUtil.hasClass(toElement, 'winfo-control-LayerType-item'))
    },
    _createLayerItem: function () {
      var typeIndex = this._layerIcon.length - 1
      while (typeIndex >= 0) {
        var key = this._layerIcon[typeIndex]
        var item = DomUtil.create('div', 'winfo-control-LayerType-item')
        item.setAttribute('title', key)
        if (typeIndex === 0) {DomUtil.addClass(item, 'active')}
        item.appendChild(this._createImage(key))
        item.appendChild(this._createNameLabel(key))
        this._layerItemDomIndex.push(key)
        this.options.isCollapsed && this.setCollapseDom(item)
        this._initLayerItemEvent(item)
        this._container.appendChild(item)
        typeIndex--
      }
    },
    setCollapseDom (item) {
      DomUtil.addClass(item, 'position')
      var index = this._layerItemDomIndex.indexOf(item.title)
      var opacity = this.options.hiddenDomOpacity
      if (index === this.options.layerType.length - 1) {opacity = 1}
      item.style.right = (this.options.layerType.length - index) *
        this.options.offsetCollapsed + 'px'
      item.style.zIndex = index
      item.style.opacity = opacity
    },
    setExpandDom (item) {
      DomUtil.removeClass(item, 'position')
      item.style.opacity = 1
      item.style.right = ''
    },
    _initLayerItemEvent: function (item) {
      var that = this
      DomEvent.on(item, 'click', function (e) {
        var name = this.getAttribute('title')
        if (name === that._currentLayer) return
        that._removeAllItemActive(this.parentNode.children)
        DomUtil.addClass(this, 'active')
        that.addNewLayer(name)
        that.removeCurrentLayer()
        that._currentLayer = name
        let allLayer = [...that.options.layerType]
        allLayer.splice(allLayer.indexOf(that._currentLayer), 1)
        allLayer.unshift(that._currentLayer)
        that.options.toggleCallback && that.options.toggleCallback(allLayer)
      })
      DomEvent.on(item, 'touchstart', function (e) {})
    },
    _removeAllItemActive (children) {
      if (!children.length) return
      for (let key in children) {
        if (!isNaN(children[key])) continue
        children[key].classList && DomUtil.removeClass(children[key], 'active')
      }
    },
    _collapse: function () {},
    remove: function () {},
    addBaseLayer: function () {},
    expand: function () {},
    collapse: function () {},
    _getLayer: function () {},
    _update: function () {},
    _onLayerChange: function () {},
    _createRadioElement: function () {},
    _addItem: function () {},
    _onInputClick: function () {},
    _checkDisabledLayers: function () {},
    _expandIfNotCollapsed: function () {},
    _expand: function () {}
  })
  var TyphoonPath = Class.extend({
    options: {
      className: 'Typhoon',
      startPoint: {
        showToolTip: true,
        toolTipOptions: {},
        toolTipTemplate: false
      },
      endPoint: {
        showToolTip: true,
        toolTipOptions: {},
        toolTipTemplate: false
      },
      mouse: {
        showToolTip: true,
        toolTipOptions: {
          direction: 'top',
          sticky: 'false'
        },
        toolTipTemplate: false,
        callback: false
      },
      click: {
        showToolTip: true,
        toolTipOptions: {
          direction: 'top',
          sticky: 'true',
          className: 'typhoonClickPopup'
        },
        toolTipTemplate: false,
        callback: false
      },
      windCoil: {
        show: true,
        clickable: true,
        color: '#eea01d',
        fill: true,
        fillColor: '#eea01d',
        fillOpacity: 0.5,
        opacity: 0.3,
        stroke: true,
        weight: 1,
        showImage: true,
        image: new Icon({
          iconUrl: '',
          iconSize: [30, 30]
        }),
        Multiple: 1000
      },
      loginMarker: {
        showImage: true,
        image: new Icon({
          iconUrl: '',
          iconSize: [30, 30],
          iconAnchor: [6, 30]
        }),
        toolTipOptions: {
          sticky: true,
          direction: 'top',
          showWhenon: false,
          className: 'typhoonTooltip'
        }
      },
      forecastPoint: { show: true },
      defaultStyle: {
        color: 'rgb(125,125,125)',
        radius: 5,
        weight: 1,
        opacity: 1,
        fillOpacity: 1
      },
      lineStyle: {
        color: 'rgb(0, 118, 201)',
        weight: 2
      },
      events: { drawOverCallBack: false },
      legend: {
        '热带风暴': { fillColor: 'rgb(88, 138, 246)' },
        '强热带风暴': { fillColor: 'rgb(248, 249, 42)' },
        '台风': { fillColor: 'rgb(250, 171, 42)' },
        '强台风': { fillColor: 'rgb(250, 131, 246)' },
        '超强台风': { fillColor: 'rgb(255, 0, 0)' },
        '中国': {
          color: 'rgb(255, 0, 0)',
          dashArray: '5,5'
        },
        '日本': {
          color: 'rgb(43, 190, 0)',
          dashArray: '5,5'
        },
        '中国台湾': {
          color: 'rgb(255, 0, 255)',
          dashArray: '5,5'
        },
        '中国香港': {
          color: 'rgb(254, 145, 4)',
          dashArray: '5,5'
        },
        '美国': {
          color: 'rgb(17, 247, 247)',
          dashArray: '5,5'
        }
      },
      speed: 10
    },
    _sourceData: {},
    typhoonData: {},
    _WindCoilNumDic: {
      '7': '七',
      '10': '十',
      '12': '十二'
    },
    _cordonLine: {
      polyline24: null,
      marker24: null,
      polyline48: null,
      marker48: null
    },
    initialize: function (options) {
      extendOptions(this.options, options)
      return this
    },
    addTo: function (map) {
      this._map = map
      this._initCordonLine()
      return this
    },
    _initCordonLine: function () {
      this._cordonLine.polyline24 = polyline(
        [[34, 127], [22, 127], [18, 120], [11, 120], [4.5, 113], [0, 105]], {
          color: '#fff200',
          weight: 1.5
        }).addTo(this._map)
      this._cordonLine.polyline48 = polyline(
        [[34, 132], [15, 132], [0, 120], [0, 105]], {
          color: '#0303ff',
          weight: 0.8,
          dashArray: '6,3'
        }).addTo(this._map)
      this._cordonLine.marker48 = marker([26, 132], {
        icon: icon({
          iconAnchor: [0, 30],
          iconSize: [14, 75],
          iconUrl: ''
        })
      }).addTo(this._map)
      this._cordonLine.marker24 = marker([26, 127], {
        icon: icon({
          iconAnchor: [0, 30],
          iconSize: [14, 75],
          iconUrl: ''
        })
      }).addTo(this._map)
    },
    setTyphoon: function (data) {
      var setTyphoonId = []
      for (var i = 0; i < data.length; i++) {
        var typhoonItem = data[i]
        this._sourceData[typhoonItem.tfid] = extend({}, typhoonItem)
        this.typhoonData[typhoonItem.tfid] = this.initTyphoonData(
          extend({}, typhoonItem))
        setTyphoonId.push(typhoonItem.tfid)
      }
      return setTyphoonId
    },
    setDrawToIndex (typhoonid, index) {
      let typhoonData = extend({}, this._sourceData[typhoonid])
      this.removeTyphoonById(typhoonid)
      this.setTyphoon([typhoonData])
      this.typhoonData[typhoonid].drawToIndex = index
      this.typhoonData[typhoonid].nowDrawIndex = 0
      this.drawingTyphoon(this.typhoonData[typhoonid], true)
    },
    initTyphoonData: function (typhoonItem) {
      var typhoonDataDrawData = {
        nowDrawIndex: 0,
        pointLength: typhoonItem.points.length,
        drawToIndex: typhoonItem.points.length,
        polyline: new Polyline([], this.getPolyLineStyle()).addTo(this._map),
        polylinePoint: [],
        loginMarkder: [],
        loadMarkerTooltip: [],
        windCoilMarker: null,
        windCoil7: null,
        windCoil10: null,
        windCoil12: null,
        forecastData: {},
        id: typhoonItem.tfid,
        className: this.options.className + typhoonItem.tfid
      }
      typhoonItem.points = this.initTyphoonPoint(typhoonItem)
      return extend(typhoonDataDrawData, typhoonItem)
    },
    initTyphoonPoint ({ land, points }) {
      let dataByTimeStamp = {}
      let dataTimeStamp = []
      if (!points.length) return []
      points.map(d => {
        d.pointType = 'path'
        let timeStamp = timeStringToTimeStamp(d.time, ' ', '/', ':')
        dataTimeStamp.push(timeStamp)
        dataByTimeStamp[timeStamp] = d
      })
      if (land && land.length) {
        land.map(d => {
          d.pointType = 'land'
          let timeStamp = timeStringToTimeStamp(d.landtime, ' ', '/', ':')
          dataTimeStamp.push(timeStamp)
          dataByTimeStamp[timeStamp] = d
        })
      }
      dataTimeStamp.sort()
      let newPoints = []
      dataTimeStamp.map(d => {newPoints.push(dataByTimeStamp[d])})
      return newPoints
    },
    removeTyphoonsById: function (id) {
      if (id.length) {
        id.map(d => {this.removeTyphoonById(d)})
      } else {
        if (this.typhoonData.hasOwnProperty(id)) {
          this.removeTyphoonById(id)
        }
      }
    },
    removeTyphoonById: function (id) {
      let typhoon = this.typhoonData[id]
      this.removeloginMarkder(typhoon)
      typhoon.land = []
      typhoon.points = []
      typhoon.nowDrawIndex = 0
      typhoon.polyline && typhoon.polyline.remove()
      typhoon.polylinePoint.length &&
      (typhoon.polylinePoint.map(d => d.remove()))
      typhoon.loginMarkder.length &&
      (typhoon.loginMarkder.map(d => d.remove()))
      typhoon.windCoil7 && typhoon.windCoil7.remove()
      typhoon.windCoil10 && typhoon.windCoil10.remove()
      typhoon.windCoil12 && typhoon.windCoil12.remove()
      typhoon.windCoilMarker && typhoon.windCoilMarker.remove()
      for (let key in typhoon.forecastData) {
        if (typhoon.forecastData.hasOwnProperty(key)) {
          let forecastItem = typhoon.forecastData[key]
          forecastItem.polyline && forecastItem.polyline.remove()
          forecastItem.polylinePoint && forecastItem.polylinePoint.length &&
          forecastItem.polylinePoint.map(d => d.remove())
        }
      }
      delete this._sourceData[id]
      delete this.typhoonData[id]
    },
    onRemove: function () {
      for (let key in this.typhoonData) {
        if (this.typhoonData.hasOwnProperty(key)) {this.removeTyphoonById(key)}
      }
      this._cordonLine.polyline24 && this._cordonLine.polyline24.remove()
      this._cordonLine.polyline48 && this._cordonLine.polyline48.remove()
      this._cordonLine.marker48 && this._cordonLine.marker48.remove()
      this._cordonLine.marker24 && this._cordonLine.marker24.remove()
    },
    addTyphoon: function (data) {
      this.drawTyphoon(this.setTyphoon(data))
      return this
    },
    drawTyphoon: function (tfids) {
      for (var i = 0; i < tfids.length; i++) {
        this.drawingTyphoon(this.typhoonData[tfids[i]])
      }
    },
    drawingTyphoon: function (typhoon, noPlay) {
      if (typhoon.nowDrawIndex >= typhoon.pointLength) {
        this.options.events.drawOverCallBack &&
        this.options.events.drawOverCallBack(typhoon)
        return
      }
      if (typhoon.nowDrawIndex > typhoon.drawToIndex) return
      if (noPlay) {
        this.drawingTyphoonIndex(typhoon, noPlay)
      } else {
        let index = setTimeout(() => {
          clearTimeout(index)
          this.drawingTyphoonIndex(typhoon)
        }, this.options.speed)
      }
    },
    drawingTyphoonIndex (typhoon, noPlay) {
      if (!(typhoon.points && typhoon.points.length)) return
      let options = this.options
      this.drawPolyline(typhoon)
      this.drawPoint(typhoon)
      options.windCoil.show && this.drawWindCoil(typhoon)
      options.windCoil.showImage && this.drawWindCoilIcon(typhoon)
      if (options.forecastPoint.show) {
        noPlay && this.drawForecastData(typhoon)
        !noPlay && this.isLastPoint(typhoon) && this.drawForecastData(typhoon)
      }
      typhoon.nowDrawIndex++
      this.drawingTyphoon(typhoon, noPlay)
    },
    drawPolyline: function (typhoon) {
      typhoon.polyline.addLatLng(this.getCurrPointLngLat(typhoon))
    },
    drawPoint: function (typhoon) {
      let pointData = typhoon.points[typhoon.nowDrawIndex]
      let pointType = pointData.pointType
      pointData.id = typhoon.id
      pointData.name = typhoon.name
      pointData.enname = typhoon.enname
      pointData.className = typhoon.className
      let options = this.options
      let typhoonPoint = new CircleMarker(this.getCurrPointLngLat(typhoon),
        this.getStyleByKey(pointData.strong)).addTo(this._map)
      if ((pointType === 'land') && options.loginMarker.showImage) {
        this.drawLoginMarker({
          pointData,
          typhoon
        })
      }
      if (typhoon.nowDrawIndex === 0 && options.startPoint.showToolTip) {
        typhoonPoint.hasToolTip = true
        this.autoToopTip({
          dom: typhoonPoint,
          content: this.getStartorEndToolTipTemplate({
            pointData,
            typhoon,
            type: 'start'
          }),
          options: { permanent: true }
        })
      }
      if (this.isLastPoint(typhoon) && options.endPoint.showToolTip) {
        typhoonPoint.hasToolTip = true
        this.autoToopTip({
          dom: typhoonPoint,
          content: this.getStartorEndToolTipTemplate({
            pointData,
            typhoon,
            type: 'end'
          }),
          options: { permanent: true }
        })
      }
      typhoon.polylinePoint.push(typhoonPoint)
      this.openMouseEvent({
        dom: typhoonPoint,
        pointData,
        typhoon
      })
    },
    toggleMouseOverToolTip: function ({ eventType, dom, pointData, typhoon, pointType }) {
      if (eventType === 'mouseout') {
        dom.setRadius(dom._radius -= 2)
      } else {
        if (eventType === 'mouseover') {
          dom.setRadius(dom._radius += 2)
          dom.hasToolTip || this.autoToopTip({
            dom,
            content: this.getTyphoonToolTipTempalte({
              eventType,
              dom,
              pointData,
              typhoon,
              pointType
            }),
            options: this.options.mouse.toolTipOptions
          })
        } else {
          dom.hasToolTip || this.autoPopup({
            dom,
            content: this.getTyphoonToolTipTempalte({
              eventType,
              dom,
              pointData,
              typhoon,
              pointType
            }),
            options: this.options.click.toolTipOptions
          })
        }
      }
    },
    autoPopup ({ dom, content, options }) {
      dom.bindPopup(content, options).
      openPopup()
    },
    autoToopTip: function ({ dom, content, options }) {
      options.className = options.className
        ? options.className
        : dom.className
      dom.bindTooltip(content, options).openTooltip()
    },
    drawLoginMarker: function ({ pointData, typhoon }) {
      let marker = new Marker([pointData.lat, pointData.lng],
        { icon: this.options.loginMarker.image }).addTo(this._map)
      if (this.options.loginMarker.toolTipOptions.showWhenon) {
        let content = this.getTyphoonToolTipTempalte({
          eventType: 'mouse',
          dom: marker,
          pointData,
          typhoon,
          pointType: 'land'
        })
        let tooltip = new Tooltip(this.options.loginMarker.toolTipOptions)
        tooltip.setLatLng([pointData.lat, pointData.lng])
        tooltip.setContent(content)
        tooltip.addTo(this._map)
        typhoon.loadMarkerTooltip.push(tooltip)
      }
      typhoon.loginMarkder.push(marker)
    },
    removeloginMarkder (typhoon) {
      typhoon.loadMarkerTooltip.length &&
      typhoon.loadMarkerTooltip.map(d => d.remove())
      typhoon.loadMarkerTooltip = []
    },
    getStartorEndToolTipTemplate: function ({ pointData, typhoon, type }) {
      if (this.options[type + 'Point'].toolTipTemplate) {
        return this.options[type + 'Point'].toolTipTemplate({
          pointData,
          typhoon
        })
      }
      if (type == 'start') {
        return typhoon.id + typhoon.name
      } else {return typhoon.name + typhoon.endtime}
    },
    getPolyLineStyle: function () {
      var defaultStyle = extend({}, this.options.defaultStyle)
      var lineStyle = extend(defaultStyle, this.options.lineStyle)
      return lineStyle
    },
    drawWindCoil: function (typhoon) {
      let currPointData = typhoon.points[typhoon.nowDrawIndex]
      if (currPointData.pointType === 'land') return
      let options = this.options.windCoil
      let windCoilNum = [12, 10, 7]
      let windCoilMultiple = options.Multiple
      for (let i = 0; i < windCoilNum.length; i++) {
        let num = windCoilNum[i]
        let svgOverlayKey = 'windCoil' + num
        let radiusDataKey = 'radius' + num
        if (typhoon[svgOverlayKey]) {typhoon[svgOverlayKey].remove()}
        let radiusData = currPointData[radiusDataKey].split('|')
        if (radiusData < 4) continue
        let sourceData = extend({}, currPointData)
        let drawData = {
          NorthEast: radiusData[0] * windCoilMultiple,
          SouthEast: radiusData[1] * windCoilMultiple,
          NorthWest: radiusData[2] * windCoilMultiple,
          SouthWest: radiusData[3] * windCoilMultiple,
          windCoil: num + ''
        }
        extend(drawData, sourceData)
        extend(drawData, options)
        let windCoil = new WindCoilCircle(this.getCurrPointLngLat(typhoon),
          drawData).addTo(this._map)
        windCoil.windData = radiusDataKey
        windCoil.bringToBack()
        typhoon[svgOverlayKey] = windCoil
        windCoil.pointType = 'windCoil'
        this.openMouseEvent({
          dom: windCoil,
          pointData: currPointData,
          typhoon
        })
      }
    },
    getWindCoilData: function (options) {
      let html = ''
      let Mult = this.options.windCoil.Multiple
      html += '<div>' + this._WindCoilNumDic[options.windCoil] + '级风圈</div>'
      html += '<div>西北:' + options.SouthWest / Mult + 'km | 东北:' +
        options.NorthEast / Mult + 'km</div>'
      html += '<div>西南:' + options.NorthWest / Mult + 'km | 东南:' +
        options.SouthEast / Mult + 'km</div>'
      return html
    },
    drawForecastData: function (typhoon) {
      let forecast = typhoon.points[typhoon.nowDrawIndex].forecast
      if (!forecast) return
      for (let i = 0; i < forecast.length; i++) {
        let list = forecast[i]
        try {
          typhoon.forecastData[list.tm].polyline.remove()
          typhoon.forecastData[list.tm].polylinePoint.map(d => d.remove())
        } catch (e) {}
        typhoon.forecastData[list.tm] = {
          polyline: new Polyline([], this.getStyleByKey(list.tm)).addTo(
            this._map),
          polylinePoint: []
        }
        for (let n = 0; n < list.forecastpoints.length; n++) {
          let item = list.forecastpoints[n]
          item.id = typhoon.id
          item.name = typhoon.name
          item.enname = typhoon.enname
          item.pointType = 'forecast'
          var lnglat = new LatLng(Number(item.lat), Number(item.lng))
          typhoon.forecastData[list.tm].polyline.addLatLng(lnglat)
          var forecasePoint = new CircleMarker(lnglat,
            this.getForecastPointStyle(item)).addTo(this._map)
          typhoon.forecastData[list.tm].polylinePoint.push(forecasePoint)
          this.openMouseEvent({
            dom: forecasePoint,
            pointData: item,
            typhoon
          })
        }
      }
    },
    openMouseEvent: function ({ dom, pointData, typhoon }) {
      let options = this.options
      dom.on('mouseover mouseout click', ev => {
        this.removeloginMarkder(typhoon)
        let eventType = ev.type
        switch (eventType) {
          case'mouseover':
          case'mouseout':
            options.mouse.showToolTip && this.toggleMouseOverToolTip({
              eventType,
              dom,
              pointData,
              typhoon,
              pointType: pointData.pointType
            })
            options.mouse.callback && options.mouse.callback({
              eventType,
              dom,
              pointData,
              typhoon,
              pointType: pointData.pointType
            })
            break
          case'click':
            if (Browser.mobile) {
              options.click.showToolTip && this.toggleMouseOverToolTip({
                eventType,
                dom,
                pointData,
                typhoon,
                pointType: pointData.pointType
              })
              options.click.callback && options.click.callback({
                eventType,
                dom,
                pointData,
                typhoon,
                pointType: pointData.pointType
              })
              break
            }
        }
      })
    },
    getForecastPointStyle: function (item) {
      var defaultStyle = extend({}, this.options.defaultStyle)
      var power = Number(item.power)
      if (power >
        7) {defaultStyle.fillColor = 'rgb(88, 138, 246)'} else if (power >
        5) {defaultStyle.fillColor = 'rgb(81, 251, 82)'} else {defaultStyle.fillColor = 'rgb(255, 255, 255)'}
      var pointStyle = extend(defaultStyle, {})
      return pointStyle
    },
    getCurrPointLngLat: function (typhoon) {
      var point = typhoon.points[typhoon.nowDrawIndex]
      return new LatLng(Number(point.lat), Number(point.lng))
    },
    drawWindCoilIcon: function (typhoon) {
      typhoon.windCoilMarker && typhoon.windCoilMarker.remove()
      var point = this.getCurrPointLngLat(typhoon)
      typhoon.windCoilMarker = new Marker([point.lat, point.lng],
        { icon: this.options.windCoil.image }).addTo(this._map)
    },
    getStyleByKey: function (key) {
      var defaultStyle = extend({}, this.options.defaultStyle)
      var pointStyle = extend(defaultStyle, this.options.legend[key])
      return pointStyle
    },
    isLastPoint: function (data) {
      return data.nowDrawIndex === data.pointLength - 1
    },
    defaltPathTemplate: function (pointData) {
      var html = ''
      html += '<div class="winfo-tooltip-title">' + pointData.name + '    ' +
        pointData.time + '</div>'
      html += '<div class="winfo-tooltip-row">' +
        '<div class="winfo-tooltip-label">中心位置</div>' + pointData.lat +
        ' / ' + pointData.lng + '</div>'
      html += '<div class="winfo-tooltip-row winfo-tooltip-topLine">' +
        '<div class="winfo-tooltip-label">风速风力</div>' + pointData.speed +
        '米/秒, ' + pointData.power + '级' + pointData.strong + '</div>'
      html += '<div class="winfo-tooltip-row winfo-tooltip-topLine">' +
        '<div class="winfo-tooltip-label">中心气压</div>' + pointData.pressure +
        '百帕' + '</div>'
      html += '<div class="winfo-tooltip-row winfo-tooltip-topLine">' +
        '<div class="winfo-tooltip-label">移速移向</div>' + pointData.movespeed +
        '公里/小时, ' + pointData.movedirection + '</div>'
      var radius7 = this.getradiusRange(pointData.radius7)
      radius7 &&
      (html += '<div class="winfo-tooltip-row winfo-tooltip-topLine">' +
        '<div class="winfo-tooltip-label">七级风圈</div>' + radius7.min + '~' +
        radius7.max + '</div>')
      var radius10 = this.getradiusRange(pointData.radius10)
      radius10 &&
      (html += '<div class="winfo-tooltip-row winfo-tooltip-topLine">' +
        '<div class="winfo-tooltip-label">十级风圈</div>' + radius10.min + '~' +
        radius10.max + '</div>')
      var radius12 = this.getradiusRange(pointData.radius12)
      radius12 &&
      (html += '<div class="winfo-tooltip-row winfo-tooltip-topLine">' +
        '<div class="winfo-tooltip-label">十二级风圈</div>' + radius12.min + '~' +
        radius12.max + '</div>')
      return html
    },
    defaltLandTemplate: function (pointData) {
      var html = ''
      html += '<div class="winfo-tooltip-title">' + pointData.name + '    ' +
        pointData.id + ' 登录</div>'
      html += '<div class="winfo-tooltip-row">' + pointData.info + '</div>'
      return html
    },
    defalutForecastTemplate: function (pointData) {
      var html = ''
      html += '<div class="winfo-tooltip-title">' + pointData.name + '    ' +
        pointData.time + '预报</div>'
      html += '<div class="winfo-tooltip-row">' +
        '<div class="winfo-tooltip-label">当前位置</div>' + pointData.lat +
        ' / ' + pointData.lng + '</div>'
      html += '<div class="winfo-tooltip-row winfo-tooltip-topLine">' +
        '<div class="winfo-tooltip-label">中心气压</div>' + pointData.pressure +
        '百帕' + '</div>'
      html += '<div class="winfo-tooltip-row winfo-tooltip-topLine">' +
        '<div class="winfo-tooltip-label">风力</div>' + pointData.power + '级' +
        '</div>'
      return html
    },
    getTyphoonToolTipTempalte: function ({ eventType, dom, pointData, typhoon, pointType }) {
      let template = this.options[eventType.substring(0, 5)] &&
        this.options[eventType.substring(0, 5)].toolTipTemplate
      if (template) {
        return template({
          eventType,
          dom,
          pointData,
          typhoon,
          pointType
        })
      }
      if (dom.pointType === 'windCoil') {
        return this.getWindCoilData(dom.options)
      } else {
        switch (pointType) {
          case'path':
            return this.defaltPathTemplate(pointData)
          case'land':
            return this.defaltLandTemplate(pointData)
          case'forecast':
            return this.defalutForecastTemplate(pointData)
        }
      }
    },
    getradiusRange (radius) {
      var radiusData = radius.split('|')
      if (radiusData.length === 4) {
        var arr = [
          Number(radiusData[0]),
          Number(radiusData[1]),
          Number(radiusData[2]),
          Number(radiusData[3])
        ]
        return {
          max: Math.max.apply(null, arr),
          min: Math.min.apply(null, arr)
        }
      }
      return false
    }
  })
  Point.prototype.rotated = function (angle, radius) {
    return this.add(
      new Point(Math.cos(angle), Math.sin(angle)).multiplyBy(radius))
  }
  var svgUpdateWindCoilCircle = SVG.prototype._updateCircle
  SVG.include({
    _updateCircle: function (circle) {
      if (circle instanceof WindCoilCircle) {
        if (circle._empty()) {
          this._setPath(circle, 'M0 0')
        } else {
          var NorthEast = circle.getDirectionData('NorthEast')
          var SouthEast = circle.getDirectionData('SouthEast')
          var NorthWest = circle.getDirectionData('NorthWest')
          var SouthWest = circle.getDirectionData('SouthWest')
          var path = [
            'M',
            NorthEast.start.x,
            NorthEast.start.y,
            'A',
            NorthEast.radius,
            NorthEast.radius,
            0,
            0,
            1,
            NorthEast.end.x,
            NorthEast.end.y,
            'L',
            SouthEast.start.x,
            SouthEast.start.y,
            'A',
            SouthEast.radius,
            SouthEast.radius,
            0,
            0,
            1,
            SouthEast.end.x,
            SouthEast.end.y,
            'L',
            SouthWest.start.x,
            SouthWest.start.y,
            'A',
            SouthWest.radius,
            SouthWest.radius,
            0,
            0,
            1,
            SouthWest.end.x,
            SouthWest.end.y,
            'L',
            NorthWest.start.x,
            NorthWest.start.y,
            'A',
            NorthWest.radius,
            NorthWest.radius,
            0,
            0,
            1,
            NorthWest.end.x,
            NorthWest.end.y,
            'z'
          ].join(' ')
          this._setPath(circle, path)
        }
      } else {return svgUpdateWindCoilCircle.call(this, circle)}
    }
  })
  var WindCoilCircle = Circle.extend({
    options: {
      NorthEast: 0,
      SouthEast: 0,
      SouthWest: 0,
      NorthWest: 0
    },
    resetRaius: function (radius) {
      this._mRadius = radius
      this._project()
    },
    directeAngle: function (directe) {
      var directeSort = {
        'NorthEast': 0,
        'SouthEast': 1,
        'SouthWest': 2,
        'NorthWest': 3
      }
      var angle = directeSort[directe] * 90
      return {
        start: angle,
        end: angle + 90
      }
    },
    getDirectionData: function (directe) {
      this.resetRaius(this.options[directe])
      var angle = this.directeAngle(directe)
      return {
        start: this.getStartOrEndPrint(angle.start),
        end: this.getStartOrEndPrint(angle.end),
        radius: this._radius
      }
    },
    getStartOrEndPrint: function (angle) {
      return this._point.rotated((angle - 90) * Math.PI / 180, this._radius)
    }
  })

  var CanvasDrawTool = {
    getHXLine: function ({ lat, lon, sd = 0, fx = 0, length = 111.11 }) {
      let len = sd / 10
      let radian = (fx - 90) * Math.PI / 180
      let lonoffset = Math.cos(radian) * len / length
      let latoffset = Math.sin(radian) * len / length * Math.cos(lat)
      let CourseV = new Winfo.LatLng(lat + latoffset, lon + lonoffset)
      return CourseV
    },
    drawImage: function (
      ctx, { globalAlpha = 1.0, width, height, rotate = 0, path, icon }) {
      width = parseInt(width) || icon.width
      height = parseInt(height) || icon.height
      ctx.save()
      ctx.globalAlpha = globalAlpha
      ctx.translate(path[0].x, path[0].y)
      ctx.rotate(rotate * Math.PI / 180)
      ctx.drawImage(icon, 0 - width / 2, 0 - height / 2, width, height)
      ctx.restore()
    },
    getLabelBox: ({ ctx, text, path, position = 'rt',labelIcon=[] }) => {
      let labelWidth = ctx.measureText(text).width + 7 + labelIcon.length*15
      let offset = {
        lt: {
          w: -1,
          h: -1,
          textW:-labelWidth+3,
          textH:-4,
        },
        rt: {
          w: 1,
          h: -1,
          textW:3,
          textH:-4
        },
        lb: {
          w: -1,
          h: 1,
          textW:-labelWidth+3,
          textH:12

        },
        rb: {
          w: 1,
          h: 1,
          textW:3,
          textH:12
        }
      }
      position = offset[position]
      let s = {
        x: path[0].x + 15 * position.w,
        y: path[0].y + 15 * position.h
      }

      return {
        bounds: toBounds(
          [[s.x, s.y], [s.x + labelWidth * position.w, s.y + 16 * position.h]]),
        begin: s,
        offset
      }
    },
    drawShapLabel: (
      ctx,
      { pointingColor, pointingWidth, boxColor, boxWidth, boxFill, text, textColor = '#000', font, path, position = 'rt',labelIcon = []  }) => {
      let labelBox = CanvasDrawTool.getLabelBox({
        ctx,
        text,
        path,
        position,
        labelIcon,
      })
      let bound = labelBox.bounds
      CanvasDrawTool.drawLine(ctx, {
        strokeStyle: pointingColor,
        lineWidth: pointingWidth,
        path: [path[0], labelBox.begin]
      })
      ctx.globalCompositeOperation = 'source-over'
      CanvasDrawTool.drawPolygonShap(ctx, {
        fillStyle: boxFill,
        strokeStyle: boxColor,
        lineWidth: boxWidth,
        path: [
          bound.getTopLeft(),
          bound.getTopRight(),
          bound.getBottomRight(),
          bound.getBottomLeft(),
          bound.getTopLeft()
        ]
      })
      // ctx.globalCompositeOperation = 'source-over'
      CanvasDrawTool.drawText(ctx, {
        fillStyle: textColor,
        text: text,
        path: [
          {
            x: labelBox.begin.x  + labelBox.offset[position].textW,
            y: labelBox.begin.y  + labelBox.offset[position].textH,
          }
        ]
      })
      if(labelIcon.length){
        let startX = labelBox.begin.x  + labelBox.offset[position].textW + ctx.measureText(text).width + 9
        let startY = labelBox.begin.y  + labelBox.offset[position].textH - 4
        labelIcon.map((d,i)=>{
          if(fieldDiction.AIS_LABEL_ICON[d])
            CanvasDrawTool.drawImage(ctx, {
              width: 12,
              height: 12,
              rotate: 0,
              path: [{x:startX,y:startY}],
              icon: fieldDiction.AIS_LABEL_ICON[d].imgDom
            })
          startX = startX+15
          //   }
        })
      }
      return bound
    },
    drawLine: function (
      ctx, {
        strokeStyle = '#000', lineWidth = 0.8, dashArray = [
          0,
          0
        ], path
      }) {
      // ctx.save()
      ctx.strokeStyle = strokeStyle
      ctx.lineWidth = lineWidth
      ctx.setLineDash(dashArray)
      ctx.beginPath()
      for (let i = 0; i < path.length; i++) {
        i === 0 && ctx.moveTo(path[i].x, path[i].y)
        ctx.lineTo(path[i].x, path[i].y)
      }
      ctx.stroke()
      // ctx.restore()
    },
    drawText: function (ctx, { fillStyle = '#000', text, font, path }) {
      // ctx.save()
      ctx.fillStyle = fillStyle
      ctx.fillText(text, path[0].x, path[0].y)
      // ctx.restore()
    },
    drawThreeShip: function (
      ctx,
      { lineWidth = 1, fillStyle = '#fff', strokeStyle = '#000', path, rotate = 0 }) {
      let center = {
        x: path[0].x,
        y: path[0].y
      }
      let startPoint = {
        x: center.x - 5,
        y: center.y + 10
      }
      let firPoint = Util.rotatePoint(center, startPoint, rotate)
      let secPoint = Util.rotatePoint(center, {
        x: startPoint.x + 5,
        y: startPoint.y - 20
      }, rotate)
      let thrPoint = Util.rotatePoint(center, {
        x: startPoint.x + 10,
        y: startPoint.y
      }, rotate)
      CanvasDrawTool.drawPolygonShap(ctx, {
        lineWidth,
        fillStyle,
        strokeStyle,
        path: [firPoint, secPoint, thrPoint, firPoint]
      })
      return [firPoint, secPoint, thrPoint, firPoint]
    },
    drawArrow: function (
      ctx,
      { lineWidth = 1, fillStyle = '#fff', strokeStyle = '#000', path, rotate = 0 }) {
      let center = {
        x: path[0].x,
        y: path[0].y
      }
      var startPoint = {
        x: center.x - 4,
        y: center.y + 5
      }
      var firPoint = Util.rotatePoint(center, startPoint, rotate)
      var secPoint = Util.rotatePoint(center, {
        x: startPoint.x + 4,
        y: startPoint.y - 10
      }, rotate)
      var thrPoint = Util.rotatePoint(center, {
        x: startPoint.x + 8,
        y: startPoint.y
      }, rotate)
      var fouPoint = Util.rotatePoint(center, {
        x: startPoint.x + 4,
        y: startPoint.y - 3
      }, rotate)
      CanvasDrawTool.drawPolygonShap(ctx, {
        lineWidth,
        fillStyle,
        strokeStyle,
        path: [firPoint, secPoint, thrPoint, fouPoint, firPoint]
      })
    },
    drawPolygonShap: function (
      ctx, { lineWidth = 1, fillStyle = '#fff', strokeStyle = '#000', path }) {
      // ctx.save();
      ctx.fillStyle = fillStyle
      ctx.strokeStyle = strokeStyle
      ctx.lineWidth = lineWidth
      ctx.beginPath()
      ctx.moveTo(path[0].x, path[0].y)
      for (let i = 1; i < path.length; i++) {
        ctx.lineTo(path[i].x, path[i].y)
      }
      ctx.stroke()
      ctx.fill()
      // ctx.restore()
      // ctx.save()
    },
    drawClickedBox: function (ctx, { viewPos }) {
      let bouds = toBounds(viewPos)
      let padding = 5
      let length = 5
      let minX = parseInt(bouds.min.x) - padding
      let maxX = parseInt(bouds.max.x) + padding
      let minY = parseInt(bouds.min.y) - padding
      let maxY = parseInt(bouds.max.y) + padding
      CanvasDrawTool.drawLine(ctx, {
        strokeStyle: 'red',
        lineWidth: 2,
        path: [
          {
            x: minX,
            y: minY + length
          },
          {
            x: minX,
            y: minY
          },
          {
            x: minX + length,
            y: minY
          }
        ]
      })
      CanvasDrawTool.drawLine(ctx, {
        strokeStyle: 'red',
        lineWidth: 2,
        path: [
          {
            x: maxX - length,
            y: minY
          },
          {
            x: maxX,
            y: minY
          },
          {
            x: maxX,
            y: minY + length
          }
        ]
      })
      CanvasDrawTool.drawLine(ctx, {
        strokeStyle: 'red',
        lineWidth: 2,
        path: [
          {
            x: minX + length,
            y: maxY
          },
          {
            x: minX,
            y: maxY
          },
          {
            x: minX,
            y: maxY - length
          }
        ]
      })
      CanvasDrawTool.drawLine(ctx, {
        strokeStyle: 'red',
        lineWidth: 2,
        path: [
          {
            x: maxX - length,
            y: maxY
          },
          {
            x: maxX,
            y: maxY
          },
          {
            x: maxX,
            y: maxY - length
          }
        ]
      })
    },
    drawNineShip: function (
      ctx,
      { meterPerPixel, VesselLength, lineWidth = 1, fillStyle = '#fff', strokeStyle = '#000', path, rotate = 0 }) {
      let lengthPixel = VesselLength / meterPerPixel
      let center = path[0]
      let height = lengthPixel / 2
      let width = height / 4
      path = [
        // {x:center.x,y:center.y},
        {
          x: center.x + 0,
          y: center.y - 1 * height
        },
        {
          x: center.x + width / 2,
          y: center.y - 1 * (height - 3 * width / 4)
        },
        {
          x: center.x + width,
          y: center.y - 1 * (height - 9 * width / 4)
        },
        {
          x: center.x + width,
          y: center.y - 1 * (-height + width)
        },
        {
          x: center.x + 2 * width / 3,
          y: center.y - 1 * -height
        },
        {
          x: center.x + 2 * -width / 3,
          y: center.y - 1 * -height
        },
        {
          x: center.x - width,
          y: center.y - 1 * (-height + width)
        },
        {
          x: center.x - width,
          y: center.y - 1 * (height - 9 * width / 4)
        },
        {
          x: center.x - width / 2,
          y: center.y - 1 * (height - 3 * width / 4)
        },
        {
          x: center.x,
          y: center.y - 1 * height
        },
        {
          x: center.x,
          y: center.y
        }
      ]
      path = path.map(d => Util.rotatePoint(center, d, rotate))
      CanvasDrawTool.drawPolygonShap(ctx, {
        lineWidth,
        fillStyle,
        strokeStyle,
        path
      })
      return path
    },
    drawPoint: function (
      ctx,
      { radius = 3, fillStyle = 'rgb(88, 138, 246)', strokeStyle = 'rgb(88, 138, 246)', lineWidth = 1, path }) {
      ctx.fillStyle = fillStyle
      ctx.strokeStyle = strokeStyle
      ctx.lineWidth = lineWidth
      let center = path[0]
      ctx.beginPath()
      ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI)
      ctx.fill()
      ctx.stroke()
      ctx.closePath()
    },
    loadImage: function (url) {
      return new Promise((resolve) => {
        const img = new Image()
        img.src = url
        img.onload = () => {resolve(img)}
      })
    }
  }
  let AIS_TYPE_DIC = [
    {
      color: '#FF0000',
      type: '危险品船',
      subclass: [
        '散装化学品船',
        '油船(x)',
        '液化气船',
        '油船',
        '散装化学品船/油船',
        '地效翼船（装载X类危险品）',
        '地效翼船（装载Y类危险品）',
        '地效翼船（装载Z类危险品）',
        '地效翼船（装载OS类危险品）'
      ]
    },
    {
      color: '#990000',
      type: '集装箱船',
      subclass: ['集装箱船']
    },
    {
      color: '#a200ff',
      type: '渔船',
      subclass: ['渔船']
    },
    {
      color: '#FF8218',
      type: '高速船',
      subclass: ['高速船', '高速客船']
    },
    {
      color: '#663399',
      type: '工程船',
      subclass: ['工程船', '起重船']
    },
    {
      color: '#10B621',
      type: '公务船',
      subclass: [
        '执法船',
        '交通艇',
        '引航船',
        '供应船',
        '引航船舶',
        '搜救船舶',
        '拖轮',
        '港口供应船',
        '载有防污染设施和设备的船',
        '执法船舶',
        '备用：分配给当地船使用',
        '非武装冲突参与国的船舶和航空器',
        '医疗船舶',
        '医疗船'
      ]
    },
    {
      color: '#005DFF',
      type: '客船',
      subclass: ['客船', '普通客船']
    },
    {
      color: '#ff4cba',
      type: '辅助船',
      subclass: ['拖船', '普通推（拖）船', '油拖船']
    },
    {
      color: '#A56929',
      type: '货船',
      subclass: ['杂货船', '散货船', '货船', '干货船']
    },
    {
      color: '#FFFFFF',
      type: '其他船',
      subclass: [
        '疏浚或水下作业船',
        '水产品运输船',
        '特种用途船',
        '多用途船',
        '未定义',
        '其他船',
        '地效翼船',
        '拖带船',
        '长度超过200M或宽度超过25米的拖带船',
        '疏浚船或水下作业船',
        '潜水船',
        '军事行动船',
        '帆船',
        '游艇'
      ]
    }
  ]

  let fieldDiction = {
    AIS_TYPE_DIC,
    AIS_LABEL_ICON:{} //ais标签键值对  ‘重’:img
  }

  class CanvasShip extends CanvasLayer {
    constructor (options) {
      super()
      this.options = {
        zIndex: 111,
        className: 'canvasShip',
        pointStyle: {
          AISfillStyle: '#0932f5',
          AISstrokeStyle: '#0932f5',
          AISRadius: 2,
          AISlineWidth: '0.8'
        },
        shipStyle: {
          heightLineColor: 'rgba(0,0,0,0.7)',
          fillStyle: '#FFFFFF',
          strokeStyle: '#000',
          lineWidth: 0.8,
          mouseoverFillStyle: 'yellow',
          mouseoverStrokeStyle: '#f00',
          clickStrokeStyle: 'red',
          imgScale:true,// 新增
          defaultImg:false
        },
        labelStype: {
          lableFont: '300 12px Arial',
          lableTxtColor: 'rgba(0,0,0,1)',
          labelToBoxLineColor: 'rgba(0,0,0,0.8)',
          labelToBoxLineWidth: 1,
          lableBoxColor: 'rgba(255,255,255,0.8)',
          lableBoxfillColor: 'rgba(255,255,255,0.8)',
          lableBoxlineWidth: 1,
          labelThinning:true,//新增，是否标签抽稀
        },
        shipLabel:['name'],
        toolTipOptions: {
          offset: [10, 0],
          direction: 'right'
        },
        tooltipTemplater: false,
        url: {
          AISBaseUrl: `http://219.133.95.9:8001/vts-mis/DataService`,
          AISParam: `?VERSION=4.0&TRACKDATA=&ReturnCoordinate=Screen&REQUEST=GetAISList&LEVEL={level}&LNG={lng}&LAT={lat}&HEIGHT={height}&DATACONTENT={dataContent}&BBOX={bbox}`,
          customUrl: false,
          customRequest: false/** 新增**/
        },
        courseLine: {
          show: true,
          speedRatio: 111,
          clockwise: true
        },
        zoomControl: {
          pointZoom: 11,
          mouseOverToolTipDelay: 500,
          labelZoom: 13,// 支持false
          showZoom: 5
        },
        refreshDataTime: 15000,
        events: {
          drawShipsStarOrEndCallBack: false,
          requestStatusChange: false,
          mouseEventCallBack: false,
          formatData: false,
          filterData: false,
          shipNumberCallBack: false,
          shipLabelIconCustom:false
        }
      },
        this.shipNumber = 0
      this._point_Data = []
      this._sourceData = {}
      this._grid_Ship = {}
      this._selectedData = {}
      this._hoverData = {}
      this._fleetsData = {}
      this._fleetsShowMMSI = {}
      this._onlyShowFleets = false
      this._tooltip = null
      this._refreshDataTimeOutIndex = null
      this._queryTimeStamp = null
      this._locationMode = null
      this._locationModeCallback = null
      this._pointNumber = 0
      this.setOptions(options)
      this.labelBox = {} //标签框数据，用于标签抽稀
      return this
    }

    // 创建canvas层并请求数据进行绘制
    addTo (map) {
      this.add(map, {
        zIndex: this.options.zIndex,
        className: this.options.className
      })
      this.loadDicImg().then(res => {
        this.requestData()
      })
      return this
    }

    onRemove () {
      this.remove()
      this.clearTimeOut('_refreshDataTimeOutIndex')
      this.removeToolTip()
    }
    loadDicImg () {
      return new Promise(resolve => {
        let AISDIC = fieldDiction.AIS_TYPE_DIC
        let LABELDIC = fieldDiction.AIS_LABEL_ICON
        let imgIndex = []
        let imgUrl = []
        for (let i = 0; i < AISDIC.length; i++) {
          if (AISDIC[i].img) {
            imgIndex.push(`AIS_TYPE_DIC*${i}`)
            imgUrl.push(AISDIC[i].img)
          }
        }
        for (let key in LABELDIC) {
          imgIndex.push(`AIS_LABEL_ICON*${key}`)
          imgUrl.push(LABELDIC[key].img)
        }
        Util.loadImgAll(imgUrl).then(res => {
          for (let i=0;i<imgUrl.length;i++) {
            let key = imgIndex[i].split('*')
            fieldDiction[key[0]][key[1]].imgDom = res[i]
          }
          resolve()
        }).catch(res => {
          console.error('船舶图片读取有误')
        })
      })
    }

    // 刷新绘制
    drawShips () {
      this.reDraw()
    }

    // 刷新绘制
    reDraw () {
      if (!this._canvas) return
      this._changeShipNumber(0)
      if (!this._canvas) return
      this._canvas._clearContent()
      if (this._isDrawShipShap()) {this.drawShipShap()} else {
        this._changeShipNumber(this._pointNumber)
        this.drawShipPoint()
      }
    }

    // 鼠标移动的时候
    _onMouseMove (e) {
      this.removeToolTip()
      this.clearTimeOut('_mouseoverToolTipTimeOut')
      if (e.originalEvent.which === 1) return
      let ship = this._onmouseEvent(e)
      if (ship) {
        this.setCursor('pointer')
        this._hoverData = { [ship.id]: ship }
        this.drawShips()
        let delay = this.options.zoomControl.mouseOverToolTipDelay
        if (delay) {
          this._mouseoverToolTipTimeOut = setTimeout(() => {
            this.clearTimeOut('_mouseoverToolTipTimeOut')
            this.openToolTip(ship)
          }, delay)
        }
        this.options.events.mouseEventCallBack &&
        this.options.events.mouseEventCallBack({
          event: e,
          sourceData: ship
        })
      } else {
        if (JSON.stringify(this._hoverData) !== '{}') {
          this._hoverData = {}
          this.drawShips()
        }
      }
    }

    // 鼠标点击的时候
    _onMouseClick (e) {
      this.removeToolTip()
      this.clearTimeOut('_mouseoverToolTipTimeOut')
      let ship = this._onmouseEvent(e)
      if (ship) {
        this.setCursor('pointer')
        this._selectedData = { [ship.id]: ship }
        this.drawShips()
        this.options.events.mouseEventCallBack &&
        this.options.events.mouseEventCallBack({
          event: e,
          sourceData: ship
        })
      } else {
        if (JSON.stringify(this._selectedData) !== '{}' ||
          JSON.stringify(this._hoverData) !== '{}') {
          this._selectedData = {}
          this._hoverData = {}
          this.drawShips()
        }
      }
    }

    // 鼠标拖动结束后，刷新数据
    _onMoveEnd (e) {
      this.refreshCanvasData()
    }

    // 请求数据
    requestData () {
      this.clearTimeOut('_refreshDataTimeOutIndex')
      if (this._map.getZoom() < this.options.zoomControl.showZoom) {
        this._point_Data = []
        this._canvas && this.startTimeOut()
        this._canvas._clearContent()
        this._changeShipNumber('---')
        return
      }
      const staChange = this.options.events.requestStatusChange
      staChange && staChange({ status: 'start' })
      let request = this._requestData.bind(this)
      if (this.options.url.customRequest) {
        request = this.options.url.customRequest.bind(this)
      }
      request().then(res => {
        this._canvas && this.startTimeOut()
        this._changeShipNumber(0)
        this._point_Data = []
        this._sourceData = {}
        this._grid_Ship = {}
        staChange && staChange({ status: 'end' })
        let index = setTimeout(() => {
          clearTimeout(index)
          if (!this._map) return
          this._pointNumber = res.PARAM.ObjectCount
          // 用户自定义整理数据
          this.options.events.formatData &&
          (res = this.options.events.formatData(res))
          this.organizeData(res)
          this._ifLocationMode()
          this.drawShips()
        }, 0)
      })
    }

    // 发起请求
    _requestData () {
      let scriptId = document.querySelector(`#shipAIS`)
      scriptId && document.body.removeChild(scriptId)
      delete window[`jsoncallbackAIS${this._queryTimeStamp}`]
      this._queryTimeStamp = Number(new Date())
      return new Promise((resolve, reject) => {
        let center = this._map.getCenter()
        let height = this._map.getSize().y
        let zoom = this._map.getZoom()
        let box = this._boundsToString(this._map.getBounds())
        let contentType = this._isDrawShipShap() ? 2 : 0
        let scriptId = document.getElementById(`#shipAIS`)
        scriptId && document.body.removeChild(scriptId)
        let script = document.createElement('script')
        let url = null
        if (this.options.url.customUrl) {
          url = this.options.url.customUrl()
        } else {
          if (!this.options.url.AISParam) return
          url = this.options.url.AISParam.replace(/{level}/, zoom - 2).
            replace(/{lng}/, center.lng).
            replace(/{lat}/, center.lat).
            replace(/{height}/, height).
            replace(/{dataContent}/, contentType).
            replace(/{bbox}/, box) +
            `&JSONCALLBACK=jsoncallbackAIS${this._queryTimeStamp}`
          url = this.options.url.AISBaseUrl + url
        }
        script.src = url
        script.id = `#shipAIS`
        document.body.appendChild(script)
        window[`jsoncallbackAIS${this._queryTimeStamp}`] = res => {
          resolve(res)
          let scriptId = document.getElementById(`#shipAIS`)
          scriptId && document.body.removeChild(scriptId)
        }
      })
    }

    // 重绘数据与重新请求数据并绘制
    refreshCanvasData () {
      this.drawShips()
      this.requestData()
    }

    // 设置船队数据
    setFleetsData (fleetsData) {
      if (!fleetsData.length) return
      this._fleetsData = {}
      fleetsData.map(d => {
        let shipList = d.shipList
        shipList.length &&
        shipList.map(ship => {ship.drawStyle = { fillStyle: d.groupColor }})
        this._fleetsData[d.id] = d
      })
    }

    // 移除船队数据
    removeFleetsData () {
      this._fleetsData = {}
      this._fleetsShowMMSI = {}
      this._onlyShowFleets = false
      this.drawShips()
    }

    // 切换船队数据
    toggleFleetData (fleetId, show) {
      if (!this._fleetsData[fleetId]) return
      let fleetData = this._fleetsData[fleetId].shipList
      if (!fleetData.length) return
      fleetData.map(d => {
        if (show) {
          d.id = d.mmsi
          this._fleetsShowMMSI[d.mmsi] = d
        } else {delete this._fleetsShowMMSI[d.mmsi]}
      })
      this.drawShips()
    }

    // 只显示船队数据
    toggleOnlyShowFleets (toggle) {
      this._onlyShowFleets = toggle
      this.drawShips()
    }

    // 船位定位模式
    locationShipForFleets ({ fleetId, mmsi, callback }) {
      if (!this._fleetsData[fleetId]) return false
      let group = this._fleetsData[fleetId].shipList
      let hasItem = group.find(d => d.mmsi === mmsi)
      if (!hasItem) return false
      this.locationShipForData(hasItem, callback)
      return hasItem
    }

    // 用户给数据进行定位
    locationShipForData (hasItem, callback) {
      this._locationMode = hasItem.mmsi
      this._locationModeCallback = callback
      this._selectedData = { [hasItem.mmsi]: hasItem }
      let showZoom = this.options.zoomControl.pointZoom + 1
      showZoom = showZoom > 16 ? showZoom : 16
      this._map.setView(new LatLng(hasItem.lat, hasItem.lon), showZoom)
    }

    // 移除提示框
    removeToolTip () {
      this._map.hasLayer(this._tooltip) && this._map.removeLayer(this._tooltip)
    }

    // 打开信息提示框
    openToolTip (data) {
      this.removeToolTip()
      let tooltip = this._tooltip = new Tooltip(
        this.options.toolTipOptions)
      tooltip.setLatLng(data.centerPoint).
      addTo(this._map).
      setContent(this.getToolTipText(data))
    }

    // 设置信息提示框内容
    getToolTipText (data) {
      if (this.options.tooltipTemplater) {
        return this.options.tooltipTemplater(data)
      }
      return `船名:${data.NameEN}<br/>MMSI:${data.MMSI}<br/>速度:${data.SOG}节<br/>航迹向:${data.COG}°<br/>吃水:${data.Draught}米<br/>航行状态:${data.NavState}<br/>类型:${data.VesselType}<br/>纬度:${data.Lat}<br/>经度:${data.Lon}`
    }

    _onmouseEvent (e) {
      this.removeToolTip()
      this.setCursor()
      if (!this._isDrawShipShap()) return
      let ship = this._getShipContainMouse({
        x: parseInt(e.layerPoint.x),
        y: parseInt(e.layerPoint.y),
        e
      })
      if (ship && !this._canDraw(ship.id)) return false
      return ship
    }

    _getShipContainMouse ({ x, y, e }) {
      let sourceData = this._grid_Ship[parseInt(e.latlng.lat) + '_' +
      parseInt(e.latlng.lng)]
      if (!sourceData) return false
      for (let i = 0; i < sourceData.length; i++) {
        let item = this._sourceData[sourceData[i]]
        // 移动端的手指范围要按中心点距离来算
        if (Browser.mobile) {
          if (item && item.viewPos) {
            let point = this._canvas.getLayerPoint(item.CourseV)
            let pointM = new Point(x, y)
            let bounds = new Bounds(item.viewPos)
            if (point.distanceTo(pointM) < bounds.min.distanceTo(bounds.max)) {
              return item
            }
          }
        } else {
          if (item && item.viewPos && Util.isPointInPoly({
            x,
            y
          }, item.viewPos)) {
            return item
          }
        }
      }
      return false
    }

    startTimeOut () {
      let timeOut = this.options.refreshDataTime
      this.clearTimeOut('_refreshDataTimeOutIndex')
      if (timeOut) {
        this._refreshDataTimeOutIndex = setTimeout(() => {
          this.clearTimeOut('_refreshDataTimeOutIndex')
          this.requestData()
        }, timeOut)
      }
    }

    clearTimeOut (index) {this[index] && clearTimeout(this[index])}

    _canDraw (id) {
      let flag1 = this._notFilter.indexOf(id.toString()) > -1
      if (this._onlyShowFleets) {
        let flag2 = this._fleetsShowMMSI.hasOwnProperty(id)
        return flag1 & flag2
      } else {return flag1}
    }

    removeRegionQueryBox () {if (this._rect) this._rect.remove()}

    RegionQuery ({ callback }) {
      this._rect = new Draw.Rectangle({
        showArea: false,
        shapeOptions: {
          color: '#AC34A2',
          fillColor: '#fff',
          weight: 3,
          opacity: 1,
          fillOpacity: 0.5
        },
        events: {
          created: param => {
            callback({
              bounds: this._rect.editing._shape._bounds,
              rect: this._rect
            })
          }
        }
      }).addTo(this._map)
      this._rect.enable()
    }

    _ifLocationMode () {
      if (this._locationMode) {
        let ship = this._sourceData[this._locationMode]
        if (ship) {
          this._locationModeCallback && this._locationModeCallback(ship)
          this._selectedData[ship.id] = extendOptions({}, ship)
        } else {this._locationModeCallback && this._locationModeCallback(false)}
        this._locationMode = false
      }
    }

    _isDrawShipShap () {
      return this._map.getZoom() > this.options.zoomControl.pointZoom
    }

    organizeData (res) {
      if (!res || !res.PARAM) return
      if (!res.DATAS || !res.DATAS.length) return
      this._drawStyleFormat(res)
    }

    drawShipPoint () {
      if (!this._point_Data.length) return
      let pointStyle = this.options.pointStyle
      this._point_Data.map(d => {
        CanvasDrawTool.drawPoint(this._canvas._ctx, {
          strokeStyle: pointStyle.AISstrokeStyle,
          fillStyle: pointStyle.AISfillStyle,
          lineWidth: pointStyle.AISlineWidth,
          radius: pointStyle.AISRadius,
          path: [this._canvas.getLayerPoint(d.latLng)]
        })
      })
    }

    _drawStyleFormat (ret) {
      if (this._isDrawShipShap()) {
        return this._AISShapeStype(ret.DATAS)
      } else {
        return this._setPointStype(ret.DATAS)
      }
    }

    _setPointStype (datas) {
      datas.map(d => {
        if (d.X != undefined) {
          d.latLng = this._canvas.getContainLatLon([d.X, d.Y])
        } else {
          d.latLng = new LatLng(d.Lat, d.Lon)
        }
      })
      this._point_Data = this._point_Data.concat(datas)
    }

    _AISShapeStype (datas) {
      datas.map(d => {
        d.CourseV = CanvasDrawTool.getHXLine({
          lat: d.Lat,
          lon: d.Lon,
          sd: d.SOG,
          fx: d.COG
        })
        d.centerPoint = new LatLng(d.Lat, d.Lon)
        d.drawStyle = extend({}, this.options.shipStyle)
        this.setShipDrawStyle(d)
        d.id = d.MMSI
        d.labelIcon = this.options.events.shipLabelIconCustom?this.options.events.shipLabelIconCustom(d):[]
        d.viewPos = []
        d.NameCN = d.NameCN || (d.SBI && d.SBI.Datas && d.SBI.Datas[0]
          ? (d.SBI.Datas[0].RSDATA.NameCN || d.SBI.Datas[0].SBIDATA.NameCN)
          : d.NameEN)
        this._sourceData[d.id] = d
        let grid = parseInt(d.Lat) + '_' + parseInt(d.Lon)
        this._grid_Ship[grid] || (this._grid_Ship[grid] = [])
        this._grid_Ship[grid].push(d.id)
      })
    }

    drawShipShap () {
      this.options.events.drawShipsStarOrEndCallBack &&
      this.options.events.drawShipsStarOrEndCallBack('start')
      let shipNumber = 0
      this.labelBox = {}
      if (JSON.stringify(this._sourceData) === '{}') return
      let sourceData = extendOptions({}, this._sourceData)
      this.options.events.filterData &&
      (sourceData = this.options.events.filterData(sourceData))
      this._notFilter = Object.keys(sourceData)
      sourceData = Object.values(sourceData)
      for (let i = 0; i < sourceData.length; i++) {
        let d = sourceData[i]
        if (this._selectedData[d.id]) {
          d.zIndex = 4
        } else if (this._hoverData[d.id]) {
          d.zIndex = 3
        } else if (this._fleetsShowMMSI[d.id]) {
          d.zIndex = 2
        } else {
          d.zIndex = 1
        }
      }
      sourceData.sort((a, b) => {return a.zIndex - b.zIndex})
      sourceData.map(data => {
        // // 是否绘制航向线
        this.options.courseLine.show && data.CourseV && this.drawCourseLine(data)
        // 绘制图标或三角形状
        data.drawStyle.img ? this.drawImgShap(data) : this.drawShipShape(data)
        // 是否绘制选中框
        this._selectedData[data.id] && this.drawClickedBox(data)
        shipNumber++
      })
      sourceData.map(data => {
        // 是否绘制标题
        this.drawShipName(data)
      })
      this._changeShipNumber(shipNumber, 'add')
      this.options.events.drawShipsStarOrEndCallBack &&
      this.options.events.drawShipsStarOrEndCallBack('end')
      return false
    }

    drawImgShap (d) {
      let zoom = this._map.getZoom()
      let zoomBase = this.options.zoomControl.pointZoom
      let path = this._canvas.getLayerPoint(d.centerPoint)
      let scalc = this.options.shipStyle.imgScale?(zoom >= zoomBase ? (zoom - zoomBase) * 0.14 + 0.2 : 1):1
      scalc = scalc > 1 ? 1 : scalc
      let widthScalc = d.drawStyle.img.width * scalc
      let heightScalc =  d.drawStyle.img.height * scalc
      CanvasDrawTool.drawImage(this._canvas._ctx, {
        width: widthScalc,
        height: heightScalc,
        rotate: d.COG,
        path: [path],
        icon: d.drawStyle.img
      })
      let firstPoint = Util.rotatePoint(path,{x:path.x-widthScalc/2,y:path.y-heightScalc/2}, d.COG)
      this._sourceData[d.id].viewPos = [
        firstPoint,
        Util.rotatePoint(path,{x:path.x+widthScalc/2,y:path.y-heightScalc/2}, d.COG),
        Util.rotatePoint(path,{x:path.x+widthScalc/2,y:path.y+heightScalc/2}, d.COG),
        Util.rotatePoint(path,{x:path.x-widthScalc/2,y:path.y+heightScalc/2}, d.COG),
        firstPoint
      ]
    }

    drawClickedBox (d) {
      CanvasDrawTool.drawClickedBox(this._canvas._ctx,
        { viewPos: this._sourceData[d.id].viewPos })
    }
    drawShipName (ship) {
      if (!this.options.zoomControl.labelZoom) return
      let fleetsData = this._fleetsShowMMSI[ship.id]
      if (fleetsData ||
        (this._map.getZoom() >= this.options.zoomControl.labelZoom)) {
        let labelStyle = extend({}, this.options.labelStype)
        let name = ship.NameCN || ship.NameEN || ship.id.toString()
        if (fleetsData) {
          labelStyle.lableBoxfillColor = labelStyle.lableBoxColor = fleetsData.drawStyle.fillStyle
          name = fleetsData.shipName || name
        }

        let path = [this._canvas.getLayerPoint(ship.centerPoint)]
        let position = this.drawLabelIntersects(ship, name,path)
        let key = `${parseInt(path[0].x/100)*100}_${parseInt(path[0].y/100)*100}`
        if (position) {
          let box = CanvasDrawTool.drawShapLabel(this._canvas._ctx, {
            pointingColor: labelStyle.labelToBoxLineColor,
            pointingWidth: labelStyle.labelToBoxLineWidth,
            boxColor: labelStyle.lableBoxColor,
            boxWidth: labelStyle.lableBoxlineWidth,
            boxFill: labelStyle.lableBoxfillColor,
            text: name,
            textColor: labelStyle.lableTxtColor,
            path: path,
            position,
            labelIcon:ship.labelIcon
          })
          this.labelBox[key] || (this.labelBox[key]=[])
          this._map.getZoom()>=16?this.labelBox[key].push(box):this.labelBox[key].push(toBounds([[box.min.x-60,box.min.y-60],[box.max.x+60,box.max.y+60]]))
        }
      }
    }

    /** 标签抽稀**/
    drawLabelIntersects (ship, name, path) {
      if(!Object.values(this.labelBox).length)return 'rt'
      let rowKey = parseInt(path[0].x/100)*100
      let colKey = parseInt(path[0].y/100)*100
      let neab = [
        `${rowKey-100}_${colKey-100}`,`${rowKey}_${colKey-100}`,`${rowKey+100}_${colKey-100}`,
        `${rowKey-100}_${colKey}`,`${rowKey}_${colKey}`,`${rowKey+100}_${colKey}`,
        `${rowKey-100}_${colKey+100}`,`${rowKey}_${colKey+100}`,`${rowKey+100}_${colKey+100}`
      ]

      let neabData = []
      neab.map(d=>{
        if(this.labelBox[d]){neabData = neabData.concat(this.labelBox[d])}
      })
      if(!neabData.length)return 'rt'
      let box = ['rt','lb','lt','rb']
      for(let i = 0;i<box.length;i++){
        let flag = false
        positionType:
          for(let y = 0;y< neabData.length; y++){
            let bounds = CanvasDrawTool.getLabelBox({ ctx: this._canvas._ctx, text: name, path,position:box[i],labelIcon:ship.labelIcon }).bounds
            if (neabData[y].intersects(bounds)) {
              flag = true
              break positionType
            }
          }
        if(!flag){
          return box[i]
        }
      }
      return (this._map.getZoom()>=16||ship.zIndex>1)?'lb':(this.options.labelStype.labelThinning?false:'rt')
    }

    drawShipShape (shipdata) {
      let drawData = extend({}, shipdata.drawStyle)
      let isHover = Boolean(this._hoverData[shipdata.id])
      let isFleet = this._fleetsShowMMSI[shipdata.id]
      let meterPerPixel = this._meters2pixel()
      let lengthPixel = shipdata.VesselLength / meterPerPixel
      let style = {
        lineWidth: drawData.lineWidth,
        fillStyle: Boolean(isFleet) ? isFleet.drawStyle.fillStyle : (isHover
          ? drawData.mouseoverFillStyle
          : drawData.fillStyle),
        strokeStyle: isHover
          ? drawData.mouseoverStrokeStyle
          : drawData.strokeStyle,
        rotate: shipdata.COG,
        path: [this._canvas.getLayerPoint(shipdata.centerPoint)],
        meterPerPixel,
        VesselLength: shipdata.VesselLength
      }
      this._sourceData[shipdata.id].viewPos = shipdata.viewPos = (lengthPixel >
        25 && shipdata.VesselLength < 500) ?
        CanvasDrawTool.drawNineShip(this._canvas._ctx, style) :
        CanvasDrawTool.drawThreeShip(this._canvas._ctx, style)
    }

    drawCourseLine (shipdata) {
      let Course = this._canvas.getLayerPoint(shipdata.centerPoint)
      let CourseV = this._canvas.getLayerPoint(shipdata.CourseV)
      let param = extend({}, shipdata.drawStyle)
      param.strokeStyle = param.heightLineColor
      param.path = [
        {
          x: Course.x,
          y: Course.y
        },
        {
          x: CourseV.x,
          y: CourseV.y
        }
      ]
      CanvasDrawTool.drawLine(this._canvas._ctx, param)
    }

    _boundsToString (bounds) {
      return bounds._southWest.lng + ',' + bounds._southWest.lat + ',' +
        bounds._northEast.lng + ',' + bounds._northEast.lat
    }

    _meters2pixel () {
      let e = this._map.getSize().y / 2
      return this._map.distance(this._canvas.getContainLatLon([0, e]),
        this._canvas.getContainLatLon([100, e])) / 100
    }

    _changeShipNumber (num, type) {
      if (type) {this.shipNumber += num} else {this.shipNumber = num}
      this.options.events.shipNumberCallBack &&
      this.options.events.shipNumberCallBack(this.shipNumber)
    }
    setShipDrawStyle (data) {
      let type = fieldDiction.AIS_TYPE_DIC.find(
        d => d.subclass.indexOf(data.VesselType) > -1)
      if (!type) {
        type = fieldDiction.AIS_TYPE_DIC[fieldDiction.AIS_TYPE_DIC.length-1]
      }
      type.img
        ? (data.drawStyle.img = type.imgDom)
        : (data.drawStyle.fillStyle = type.color)
    }

    getShapData () {
      let dataArr = []
      for (let key in this._sourceData) {
        let item = extendOptions({}, this._sourceData[key])
        item.shap = { _latlng: new LatLng(item.Lat, item.Lon) }
        dataArr.push(item)
      }
      return dataArr
    }
  }

  let LatLngGraticule = Layer.extend({
    options: {
      showLabel: true,
      opacity: 1,
      weight: 0.8,
      color: '#222',
      fontColor: '#222',
      font: '12px Verdana',
      dashArray: [0, 0],
      lngLineCurved: 0,
      latLineCurved: 0,
      zoomInterval: [
        {
          start: 2,
          end: 2,
          interval: 40
        },
        {
          start: 3,
          end: 3,
          interval: 20
        },
        {
          start: 4,
          end: 4,
          interval: 10
        },
        {
          start: 5,
          end: 6,
          interval: 5
        },
        {
          start: 7,
          end: 9,
          interval: 1
        },
        {
          start: 10,
          end: 12,
          interval: 0.1666666666667
        },
        {
          start: 13,
          end: 20,
          interval: 0.01666666666667
        }
      ]
    },
    initialize: function (options) {
      setOptions(this, options || {})
      var defaultFontName = 'Verdana'
      var _ff = this.options.font.split(' ')
      if (_ff.length < 2) {this.options.font += ' ' + defaultFontName}
      if (!this.options.fontColor) {this.options.fontColor = this.options.color}
      if (this.options.zoomInterval) {
        if (this.options.zoomInterval.latitude) {
          this.options.latInterval = this.options.zoomInterval.latitude
          if (!this.options.zoomInterval.longitude) {this.options.lngInterval = this.options.zoomInterval.latitude}
        }
        if (this.options.zoomInterval.longitude) {
          this.options.lngInterval = this.options.zoomInterval.longitude
          if (!this.options.zoomInterval.latitude) {this.options.latInterval = this.options.zoomInterval.longitude}
        }
        if (!this.options.latInterval) {this.options.latInterval = this.options.zoomInterval}
        if (!this.options.lngInterval) {this.options.lngInterval = this.options.zoomInterval}
      }
    },
    onAdd: function (map) {
      this._map = map
      if (!this._container) {this._initCanvas()}
      map._panes.overlayPane.appendChild(this._container)
      map.on('viewreset', this._reset, this)
      map.on('move', this._reset, this)
      map.on('moveend', this._reset, this)
      this._reset()
    },
    onRemove: function () {
      this._map.getPanes().overlayPane.removeChild(this._container)
      this._map.off('viewreset', this._reset, this)
      this._map.off('move', this._reset, this)
      this._map.off('moveend', this._reset, this)
    },
    addTo: function (map) {
      map.addLayer(this)
      return this
    },
    setOpacity: function (opacity) {
      this.options.opacity = opacity
      this._updateOpacity()
      return this
    },
    bringToFront: function () {
      if (this._canvas) {
        this._map._panes.overlayPane.appendChild(this._canvas)
      }
      return this
    },
    bringToBack: function () {
      var pane = this._map._panes.overlayPane
      if (this._canvas) {pane.insertBefore(this._canvas, pane.firstChild)}
      return this
    },
    getAttribution: function () {return this.options.attribution},
    _initCanvas: function () {
      this._container = DomUtil.create('div', 'winfo-image-layer')
      this._canvas = DomUtil.create('canvas', '')
      if (this._map.options.zoomAnimation && Browser.any3d) {
        DomUtil.addClass(this._canvas, 'winfo-zoom-animated')
      } else {DomUtil.addClass(this._canvas, 'winfo-zoom-hide')}
      this._updateOpacity()
      this._container.appendChild(this._canvas)
      extend(this._canvas, {
        onselectstart: Util.falseFn,
        onmousemove: Util.falseFn,
        onload: bind(this._onCanvasLoad, this)
      })
    },
    _reset: function () {
      var container = this._container
      var canvas = this._canvas
      var size = this._map.getSize()
      var lt = this._map.containerPointToLayerPoint([0, 0])
      DomUtil.setPosition(container, lt)
      container.style.width = size.x + 'px'
      container.style.height = size.y + 'px'
      canvas.width = size.x
      canvas.height = size.y
      canvas.style.width = size.x + 'px'
      canvas.style.height = size.y + 'px'
      this.__calcInterval()
      this.__draw(true)
    },
    _onCanvasLoad: function () {this.fire('load')},
    _updateOpacity: function () {
      DomUtil.setOpacity(this._canvas, this.options.opacity)
    },
    __format_lat: function (lat) {
      if (lat > 90 || lat < -90) return ''
      if (this.options.latFormatTickLabel) {
        return this.options.latFormatTickLabel(lat)
      }
      let degree = Util.conversionDegreeAndGeo.GeoToLatDegree(lat)
      return degree[0] + '°' + degree[1] + '′' + degree[2] + '″' + degree[3]
    },
    __format_lng: function (lng) {
      if (lng > 180 || lng < -180) return ''
      if (this.options.lngFormatTickLabel) {
        return this.options.lngFormatTickLabel(lng)
      }
      let degree = Util.conversionDegreeAndGeo.GeoToLngDegree(lng)
      return degree[0] + '°' + degree[1] + '′' + degree[2] + '″' + degree[3]
    },
    __calcInterval: function () {
      var zoom = this._map.getZoom()
      if (this._currZoom != zoom) {
        this._currLngInterval = 0
        this._currLatInterval = 0
        this._currZoom = zoom
      }
      var interv
      if (!this._currLngInterval) {
        try {
          for (var idx in this.options.lngInterval) {
            var dict = this.options.lngInterval[idx]
            if (dict.start <= zoom) {
              if (dict.end && dict.end >= zoom) {
                this._currLngInterval = dict.interval
                break
              }
            }
          }
        } catch (e) {this._currLngInterval = 0}
      }
      if (!this._currLatInterval) {
        try {
          for (var idx in this.options.latInterval) {
            var dict = this.options.latInterval[idx]
            if (dict.start <= zoom) {
              if (dict.end && dict.end >= zoom) {
                this._currLatInterval = dict.interval
                break
              }
            }
          }
        } catch (e) {this._currLatInterval = 0}
      }
    },
    __draw: function (label) {
      function _parse_px_to_int (txt) {
        if (txt.length > 2) {
          if (txt.charAt(txt.length - 2) == 'p') {
            txt = txt.substr(0, txt.length - 2)
          }
        }
        try {return parseInt(txt, 10)} catch (e) {}
        return 0
      }

      var self = this, canvas = this._canvas, map = this._map,
        curvedLon = this.options.lngLineCurved,
        curvedLat = this.options.latLineCurved
      if (Browser.canvas && map) {
        if (!this._currLngInterval ||
          !this._currLatInterval) {this.__calcInterval()}
        var latInterval = this._currLatInterval,
          lngInterval = this._currLngInterval
        var ctx = canvas.getContext('2d')
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        ctx.lineWidth = this.options.weight
        ctx.strokeStyle = this.options.color
        ctx.fillStyle = this.options.fontColor
        ctx.setLineDash(this.options.dashArray)
        if (this.options.font) {ctx.font = this.options.font}
        var txtWidth = ctx.measureText('0').width
        var txtHeight = 12
        try {
          var _font_size = ctx.font.trim().split(' ')[0]
          txtHeight = _parse_px_to_int(_font_size)
        } catch (e) {}
        var ww = canvas.width
        var hh = canvas.height
        var lt = map.containerPointToLatLng(new Point(0, 0))
        var rt = map.containerPointToLatLng(new Point(ww, 0))
        var rb = map.containerPointToLatLng(new Point(ww, hh))
        var _lat_b = rb.lat
        var _lat_t = lt.lat
        var _lon_l = lt.lng
        var _lon_r = rt.lng
        var _point_per_lat = (_lat_t - _lat_b) / (hh * 0.2)
        if (isNaN(_point_per_lat)) {return}
        if (_point_per_lat < 1) {_point_per_lat = 1}
        if (_lat_b < -90) {_lat_b = -90} else {
          _lat_b = parseInt(_lat_b - _point_per_lat, 10)
        }
        if (_lat_t > 90) {_lat_t = 90} else {
          _lat_t = parseInt(_lat_t + _point_per_lat, 10)
        }
        var _point_per_lon = (_lon_r - _lon_l) / (ww * 0.2)
        if (_point_per_lon < 1) {_point_per_lon = 1}
        if (_lon_l > 0 && _lon_r < 0) {_lon_r += 360}
        _lon_r = parseInt(_lon_r + _point_per_lon, 10)
        _lon_l = parseInt(_lon_l - _point_per_lon, 10)
        var ll, latstr, lngstr, _lon_delta = 0.5

        function __draw_lat_line (self, lat_tick) {
          ll = map.latLngToContainerPoint(new LatLng(lat_tick, _lon_l))
          latstr = self.__format_lat(lat_tick)
          txtWidth = ctx.measureText(latstr).width
          if (curvedLat) {
            if (typeof (curvedLat) == 'number') {_lon_delta = curvedLat}
            var __lon_left = _lon_l, __lon_right = _lon_r
            if (ll.x > 0) {
              var __lon_left = map.containerPointToLatLng(new Point(0, ll.y))
              __lon_left = __lon_left.lng - _point_per_lon
              ll.x = 0
            }
            var rr = map.latLngToContainerPoint(
              new LatLng(lat_tick, __lon_right))
            if (rr.x < ww) {
              __lon_right = map.containerPointToLatLng(new Point(ww, rr.y))
              __lon_right = __lon_right.lng + _point_per_lon
              if (__lon_left > 0 && __lon_right < 0) {__lon_right += 360}
            }
            ctx.beginPath()
            ctx.moveTo(ll.x, ll.y)
            var _prev_p = null
            for (var j = __lon_left; j <= __lon_right; j += _lon_delta) {
              rr = map.latLngToContainerPoint(new LatLng(lat_tick, j))
              ctx.lineTo(rr.x, rr.y)
              if (self.options.showLabel && label && _prev_p != null) {
                if (_prev_p.x < 0 && rr.x >= 0) {
                  var _s = (rr.x - 0) / (rr.x - _prev_p.x)
                  var _y = rr.y - ((rr.y - _prev_p.y) * _s)
                  ctx.fillText(latstr, 0, _y + (txtHeight / 2))
                } else if (_prev_p.x <= (ww - txtWidth) && rr.x >
                  (ww - txtWidth)) {
                  var _s = (rr.x - ww) / (rr.x - _prev_p.x)
                  var _y = rr.y - ((rr.y - _prev_p.y) * _s)
                  ctx.fillText(latstr, ww - txtWidth, _y + (txtHeight / 2) - 2)
                }
              }
              _prev_p = {
                x: rr.x,
                y: rr.y,
                lon: j,
                lat: i
              }
            }
            ctx.stroke()
          } else {
            var __lon_right = _lon_r
            var rr = map.latLngToContainerPoint(
              new LatLng(lat_tick, __lon_right))
            if (curvedLon) {
              __lon_right = map.containerPointToLatLng(new Point(0, rr.y))
              __lon_right = __lon_right.lng
              rr = map.latLngToContainerPoint(
                new LatLng(lat_tick, __lon_right))
              var __lon_left = map.containerPointToLatLng(new Point(ww, rr.y))
              __lon_left = __lon_left.lng
              ll = map.latLngToContainerPoint(new LatLng(lat_tick, __lon_left))
            }
            ctx.beginPath()
            ctx.moveTo(ll.x + 1, ll.y)
            ctx.lineTo(rr.x - 1, rr.y)
            ctx.stroke()
            if (self.options.showLabel && label) {
              var _yy = ll.y + (txtHeight / 2) - 2
              ctx.fillText(latstr, 0, _yy)
              ctx.fillText(latstr, ww - txtWidth, _yy)
            }
          }
        }

        if (latInterval > 0) {
          for (var i = latInterval; i <= _lat_t; i += latInterval) {
            if (i >= _lat_b) {__draw_lat_line(this, i)}
          }
          for (var i = 0; i >= _lat_b; i -= latInterval) {
            if (i <= _lat_t) {__draw_lat_line(this, i)}
          }
        }

        function __draw_lon_line (self, lon_tick) {
          lngstr = self.__format_lng(lon_tick)
          txtWidth = ctx.measureText(lngstr).width
          var bb = map.latLngToContainerPoint(new LatLng(_lat_b, lon_tick))
          if (curvedLon) {
            if (typeof (curvedLon) == 'number') {_lat_delta = curvedLon}
            ctx.beginPath()
            ctx.moveTo(bb.x, bb.y)
            var _prev_p = null
            for (var j = _lat_b; j < _lat_t; j += _lat_delta) {
              var tt = map.latLngToContainerPoint(new LatLng(j, lon_tick))
              ctx.lineTo(tt.x, tt.y)
              if (self.options.showLabel && label && _prev_p != null) {
                if (_prev_p.y > 8 && tt.y <= 8) {
                  ctx.fillText(lngstr, tt.x - (txtWidth / 2), txtHeight)
                } else if (_prev_p.y >= hh && tt.y < hh) {
                  ctx.fillText(lngstr, tt.x - (txtWidth / 2), hh - 2)
                }
              }
              _prev_p = {
                x: tt.x,
                y: tt.y,
                lon: lon_tick,
                lat: j
              }
            }
            ctx.stroke()
          } else {
            var __lat_top = _lat_t
            var tt = map.latLngToContainerPoint(
              new LatLng(__lat_top, lon_tick))
            if (curvedLat) {
              __lat_top = map.containerPointToLatLng(new Point(tt.x, 0))
              __lat_top = __lat_top.lat
              if (__lat_top > 90) {__lat_top = 90}
              tt = map.latLngToContainerPoint(new LatLng(__lat_top, lon_tick))
              var __lat_bottom = map.containerPointToLatLng(
                new Point(bb.x, hh))
              __lat_bottom = __lat_bottom.lat
              if (__lat_bottom < -90) {__lat_bottom = -90}
              bb = map.latLngToContainerPoint(
                new LatLng(__lat_bottom, lon_tick))
            }
            ctx.beginPath()
            ctx.moveTo(tt.x, tt.y + 1)
            ctx.lineTo(bb.x, bb.y - 1)
            ctx.stroke()
            if (self.options.showLabel && label) {
              ctx.fillText(lngstr, tt.x - (txtWidth / 2), txtHeight + 1)
              ctx.fillText(lngstr, bb.x - (txtWidth / 2), hh - 3)
            }
          }
        }

        if (lngInterval > 0) {
          for (var i = lngInterval; i <= _lon_r; i += lngInterval) {
            if (i >= _lon_l) {__draw_lon_line(this, i)}
          }
          for (var i = 0; i >= _lon_l; i -= lngInterval) {
            if (i <= _lon_r) {__draw_lon_line(this, i)}
          }
        }
      }
    }
  })
  var HeatmapOverlay = Layer.extend({
    options: {
      radius: 0.3,
      maxOpacity: 0.8,
      scaleRadius: true,
      useLocalExtrema: false,
      latField: 'lat',
      lngField: 'lng',
      valueField: 'count'
    },
    initialize: function (options) {
      setOptions(this, options)
      this._el = DomUtil.create('div', 'winfo-zoom-hide')
      this._data = []
      this._max = 1
      this._min = 0
      this.options.container = this._el
      return this
    },
    onAdd: function (map) {
      var size = map.getSize()
      this._map = map
      this._width = size.x
      this._height = size.y
      this._el.style.width = size.x + 'px'
      this._el.style.height = size.y + 'px'
      this._el.style.position = 'absolute'
      this._origin = this._map.layerPointToLatLng(new Point(0, 0))
      map.getPanes().overlayPane.appendChild(this._el)
      if (!this._heatmap) {this._heatmap = h337.create(this.options)}
      map.on('moveend', this._reset, this)
      this._draw()
    },
    addTo: function (map) {
      map.addLayer(this)
      return this
    },
    onRemove: function () {
      this._map.getPanes().overlayPane.removeChild(this._el)
      this._map.off('moveend', this._reset, this)
    },
    _draw: function () {
      if (!this._map) {return}
      var mapPane = this._map.getPanes().mapPane
      var point = mapPane._winfo_pos
      this._el.style[HeatmapOverlay.CSS_TRANSFORM] = 'translate(' +
        -Math.round(point.x) + 'px,' + -Math.round(point.y) + 'px)'
      this._update()
    },
    _update: function () {
      var bounds, zoom, scale
      var generatedData = {
        max: this._max,
        min: this._min,
        data: []
      }
      bounds = this._map.getBounds()
      zoom = this._map.getZoom()
      scale = Math.pow(2, zoom)
      if (this._data.length == 0) {
        if (this._heatmap) {
          this._heatmap.setData(generatedData)
        }
        return
      }
      var latLngPoints = []
      var radiusMultiplier = this.options.scaleRadius ? scale : 1
      var localMax = 0
      var localMin = 0
      var valueField = this.options.valueField
      var len = this._data.length
      while (len--) {
        var entry = this._data[len]
        var value = entry[valueField]
        var latlng = entry.latlng
        if (!bounds.contains(latlng)) {continue}
        localMax = Math.max(value, localMax)
        localMin = Math.min(value, localMin)
        var point = this._map.latLngToContainerPoint(latlng)
        var latlngPoint = {
          x: Math.round(point.x),
          y: Math.round(point.y)
        }
        latlngPoint[valueField] = value
        var radius
        if (entry.radius) {
          radius = entry.radius * radiusMultiplier
        } else {radius = (this.options.radius || 2) * radiusMultiplier}
        latlngPoint.radius = radius
        latLngPoints.push(latlngPoint)
      }
      if (this.options.useLocalExtrema) {
        generatedData.max = localMax
        generatedData.min = localMin
      }
      generatedData.data = latLngPoints
      this._heatmap.setData(generatedData)
    },
    setData: function (data) {
      this._max = data.max || this._max
      this._min = data.min || this._min
      var latField = this.options.latField || 'lat'
      var lngField = this.options.lngField || 'lng'
      var valueField = this.options.valueField || 'value'
      var data = data.data
      var len = data.length
      var d = []
      while (len--) {
        var entry = data[len]
        var latlng = new LatLng(entry[latField], entry[lngField])
        var dataObj = { latlng: latlng }
        dataObj[valueField] = entry[valueField]
        if (entry.radius) {dataObj.radius = entry.radius}
        d.push(dataObj)
      }
      this._data = d
      this._draw()
      return this
    },
    _reset: function () {
      this._origin = this._map.layerPointToLatLng(new Point(0, 0))
      var size = this._map.getSize()
      if (this._width !== size.x || this._height !== size.y) {
        this._width = size.x
        this._height = size.y
        this._el.style.width = this._width + 'px'
        this._el.style.height = this._height + 'px'
        this._heatmap._renderer.setDimensions(this._width, this._height)
      }
      this._draw()
    }
  })
  HeatmapOverlay.CSS_TRANSFORM = (function () {
    var div = document.createElement('div')
    var props = [
      'transform',
      'WebkitTransform',
      'MozTransform',
      'OTransform',
      'msTransform'
    ]
    for (var i = 0; i < props.length; i++) {
      var prop = props[i]
      if (div.style[prop] !== undefined) {return prop}
    }
    return props[0]
  })()

  function VectorFlowLayer (options) {
    this.constructor = function (options) {
      this.animationLoop = null
      this.field = null
      this.setOptions(options)
    }
    this._defaultOption = function () {
      return {
        minVelocity: 0,
        maxVelocity: 10,
        velocityScale: 0.005,
        maxParticleAge: 90,
        particleLineWidth: 1,
        particleMultiplier: 1 / 300,
        particleReduction: Math.pow(window.devicePixelRatio, 1 / 3) || 1.6,
        frameRate: 15,
        frameTime: null,
        colorScale: [
          'rgb(36,104,180)',
          'rgb(60,157, 194)',
          'rgb(128,205,193)',
          'rgb(151,218,168)',
          'rgb(198,231,181)',
          'rgb(238,247,217)',
          'rgb(255,238,159)',
          'rgb(252,217,125)',
          'rgb(255,182,100)',
          'rgb(252,150,75)',
          'rgb(250,112,52)',
          'rgb(245,64,32)',
          'rgb(237,45,28)',
          'rgb(220,24,32)',
          'rgb(180,0,35)'
        ],
        nullWindVector: [NaN, NaN, null],
        builder: null,
        grid: null,
        gridData: null,
        date: null,
        λ0: null,
        φ0: null,
        Δλ: null,
        Δφ: null,
        ni: null,
        nj: null
      }
    }
    this.setOptions = function (options) {
      options.gridData = options.data
      delete options.data
      this.options = extend(this._defaultOption(), options)
      this.options.velocityScale = this.options.velocityScale *
        (Math.pow(window.devicePixelRatio, 1 / 3) || 1)
      this.options.frameTime = 1000 / this.options.frameRate
      this.animationLoop = null
    }
    this.setData = function (gridData) {this.options.gridData = gridData}
    this.bilinearInterpolateVector = function (
      x, y, g00, g10, g01, g11) {
      var rx = 1 - x
      var ry = 1 - y
      var a = rx * ry
      var b = x * ry
      var c = rx * y
      var d = x * y
      var u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d
      var v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d
      return [u, v, Math.sqrt(u * u + v * v)]
    }
    this.createWindBuilder = function (uComp, vComp) {
      var uData = uComp.data
      var vData = vComp.data
      return {
        header: uComp.header,
        data: function data (i) {return [uData[i], vData[i]]},
        interpolate: this.bilinearInterpolateVector
      }
    }
    this.createBuilder = function (data) {
      var uComp = null
      var vComp = null
      var scalar = null
      data.forEach(function (record) {
        switch (record.header.parameterCategory + ',' +
        record.header.parameterNumber) {
          case'1,2':
          case'2,2':
            uComp = record
            break
          case'1,3':
          case'2,3':
            vComp = record
            break
          default:
            scalar = record
        }
      })
      return this.createWindBuilder(uComp, vComp)
    }
    this.buildGrid = function (
      data, callback) {
      this.options.builder = this.createBuilder(data)
      var header = this.options.builder.header
      this.options.λ0 = header.lo1
      this.options.φ0 = header.la1
      this.options.Δλ = header.dx
      this.options.Δφ = header.dy
      this.options.ni = header.nx
      this.options.nj = header.ny
      this.options.date = new Date(header.refTime)
      this.options.date.setHours(
        this.options.date.getHours() + header.forecastTime)
      this.options.grid = []
      var p = 0
      var isContinuous = Math.floor(this.options.ni * this.options.Δλ) >= 360
      for (var j = 0; j < this.options.nj; j++) {
        var row = []
        for (var i = 0; i <
        this.options.ni; i++, p++) {row[i] = this.options.builder.data(p)}
        if (isContinuous) {row.push(row[0])}
        this.options.grid[j] = row
      }
      callback({
        date: this.options.date,
        interpolate: this.interpolate
      })
    }
    this.interpolate = function (
      λ, φ, that) {
      if (!that.options.grid) return null
      var i = that.floorMod(λ - that.options.λ0, 360) / that.options.Δλ
      var j = (that.options.φ0 - φ) / that.options.Δφ
      var fi = Math.floor(i)
      var ci = fi + 1
      var fj = Math.floor(j)
      var cj = fj + 1
      var row
      if (row = that.options.grid[fj]) {
        var g00 = row[fi]
        var g10 = row[ci]
        if (that.isValue(g00) && that.isValue(g10) &&
          (row = that.options.grid[cj])) {
          var g01 = row[fi]
          var g11 = row[ci]
          if (that.isValue(g01) && that.isValue(g11)) {
            return that.options.builder.interpolate(i - fi, j - fj, g00, g10,
              g01, g11)
          }
        }
      }
      return null
    }
    this.isValue = function (x) {return x !== null && x !== undefined}
    this.floorMod = function (a, n) {return a - n * Math.floor(a / n)}
    this.isMobile = function () {
      return /android|blackberry|iemobile|ipad|iphone|ipod|opera mini|webos/i.test(
        navigator.userAgent)
    }
    this.distort = function (
      projection, λ, φ, x, y, scale, wind) {
      var u = wind[0] * scale
      var v = wind[1] * scale
      var d = this.distortion(projection, λ, φ, x, y)
      wind[0] = d[0] * u + d[2] * v
      wind[1] = d[1] * u + d[3] * v
      return wind
    }
    this.distortion = function (projection, λ, φ, x, y) {
      varτ = 2 * Math.PI
      var H = 5
      var hλ = λ < 0 ? H : -H
      var hφ = φ < 0 ? H : -H
      var pλ = this.project(φ, λ + hλ)
      var pφ = this.project(φ + hφ, λ)
      var k = Math.cos(φ / 360 * τ)
      return [
        (pλ[0] - x) / hλ / k,
        (pλ[1] - y) / hλ / k,
        (pφ[0] - x) / hφ,
        (pφ[1] - y) / hφ
      ]
    }
    this.createField = function (columns, bounds, callback) {
      var that = this

      function field (x, y) {
        var column = columns[Math.round(x)]
        return column && column[Math.round(y)] || that.options.nullWindVector
      }

      field.release = function () {columns = []}
      field.randomize = function (o) {
        var x, y
        var safetyNet = 0
        do {
          x = Math.round(Math.floor(Math.random() * bounds.width) + bounds.x)
          y = Math.round(Math.floor(Math.random() * bounds.height) + bounds.y)
        } while (field(x, y)[2] === null && safetyNet++ < 30)
        o.x = x
        o.y = y
        return o
      }
      callback(bounds, field)
    }
    this.buildBounds = function (
      bounds, width, height) {
      var upperLeft = bounds[0]
      var lowerRight = bounds[1]
      var x = Math.round(upperLeft[0])
      var y = Math.max(Math.floor(upperLeft[1], 0), 0)
      var xMax = Math.min(Math.ceil(lowerRight[0], width), width - 1)
      var yMax = Math.min(Math.ceil(lowerRight[1], height), height - 1)
      return {
        x: x,
        y: y,
        xMax: width,
        yMax: yMax,
        width: width,
        height: height
      }
    }
    this.deg2rad = function (deg) {return deg / 180 * Math.PI}
    this.invert = function (
      x, y, windy) {
      var latlon = this.options.map.containerPointToLatLng(new Point(x, y))
      return [latlon.lng, latlon.lat]
    }
    this.project = function (
      lat, lon, windy) {
      var xy = this.options.map.latLngToContainerPoint(new LatLng(lat, lon))
      return [xy.x, xy.y]
    }
    this.interpolateField = function (
      grid, bounds, extent, callback) {
      var that = this
      var projection = {}
      var mapArea = (extent.south - extent.north) * (extent.west - extent.east)
      var velocityScale = this.options.velocityScale * Math.pow(mapArea, 0.4)
      var columns = []
      var x = bounds.x;

      function interpolateColumn (x) {
        var column = []
        for (var y = bounds.y; y <= bounds.yMax; y += 2) {
          var coord = that.invert(x, y)
          if (coord) {
            varλ = coord[0]
            varφ = coord[1]
            if (isFinite(λ)) {
              var wind = grid.interpolate(λ, φ, that)
              if (wind) {
                wind = that.distort(projection, λ, φ, x, y, velocityScale,
                  wind)
                column[y + 1] = column[y] = wind
              }
            }
          }
        }
        columns[x + 1] = columns[x] = column
      }

      (function batchInterpolate () {
        var start = Date.now()
        while (x < bounds.width) {
          interpolateColumn(x)
          x += 2
          if (Date.now() - start > 1000) {
            setTimeout(batchInterpolate, 25)
            return
          }
        }
        that.createField(columns, bounds, callback)
      })()
    }
    this.animate = function (bounds, field) {
      var that = this

      function windIntensityColorScale (
        min, max) {
        that.options.colorScale.indexFor = function (m) {
          return Math.max(0, Math.min(that.options.colorScale.length - 1,
            Math.round((m - min) / (max - min) *
              (that.options.colorScale.length - 1))))
        }
        return that.options.colorScale
      }

      var colorStyles = windIntensityColorScale(this.options.minVelocity,
        this.options.maxVelocity)
      var buckets = colorStyles.map(function () {return []})
      var particleCount = Math.round(
        bounds.width * bounds.height * this.options.particleMultiplier)
      if (this.isMobile()) {particleCount *= this.options.particleReduction}
      var fadeFillStyle = 'rgba(0, 0, 0, 0.97)'
      var particles = []
      for (var i = 0; i < particleCount; i++) {
        particles.push(field.randomize(
          {
            age: Math.floor(Math.random() * that.options.maxParticleAge) + 0
          }))
      }

      function evolve () {
        buckets.forEach(function (bucket) {bucket.length = 0})
        particles.forEach(function (particle) {
          if (particle.age > that.options.maxParticleAge) {
            field.randomize(particle).age = 0
          }
          var x = particle.x
          var y = particle.y
          var v = field(x, y)
          var m = v[2]
          if (m === null) {particle.age = that.options.maxParticleAge} else {
            var xt = x + v[0]
            var yt = y + v[1]
            if (field(xt, yt)[2] !== null) {
              particle.xt = xt
              particle.yt = yt
              buckets[colorStyles.indexFor(m)].push(particle)
            } else {
              particle.x = xt
              particle.y = yt
            }
          }
          particle.age += 1
        })
      }

      var g = this.options.canvas.getContext('2d')
      g.lineWidth = this.options.particleLineWidth
      g.fillStyle = fadeFillStyle
      g.globalAlpha = 0.6

      function draw () {
        var prev = 'lighter'
        g.globalCompositeOperation = 'destination-in'
        g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height)
        g.globalCompositeOperation = prev
        g.globalAlpha = 0.9
        buckets.forEach(function (bucket, i) {
          if (bucket.length > 0) {
            g.beginPath()
            g.strokeStyle = colorStyles[i]
            bucket.forEach(function (particle) {
              g.moveTo(particle.x, particle.y)
              g.lineTo(particle.xt, particle.yt)
              particle.x = particle.xt
              particle.y = particle.yt
            })
            g.stroke()
          }
        })
      }

      var then = Date.now();
      (function frame () {
        that.animationLoop = requestAnimationFrame(frame)
        var now = Date.now()
        var delta = now - then
        if (delta > that.options.frameTime) {
          then = now - delta % that.options.frameTime
          evolve()
          draw()
        }
      })()
    }
    this.start = function (bounds, width, height, extent) {
      var that = this
      var mapBounds = {
        south: this.deg2rad(extent[0][1]),
        north: this.deg2rad(extent[1][1]),
        east: this.deg2rad(extent[1][0]),
        west: this.deg2rad(extent[0][0]),
        width: width,
        height: height
      }
      this.stop()
      this.buildGrid(this.options.gridData, function (grid) {
        that.interpolateField(grid, that.buildBounds(bounds, width, height),
          mapBounds, function (bounds, field) {
            that.field = field
            that.animate(bounds, field)
          })
      })
    }
    this.stop = function () {
      if (this.field) this.field.release()
      if (this.animationLoop) cancelAnimationFrame(this.animationLoop)
    }
    this.constructor(options)
    return this
  }

  var FlowCanvasLayer = Layer.extend({
    initialize: function initialize (options) {
      this._map = null
      this._canvas = null
      this._frame = null
      this._delegate = null
      setOptions(this, options)
    },
    delegate: function delegate (del) {
      this._delegate = del
      return this
    },
    needRedraw: function needRedraw () {
      if (!this._frame) {
        this._frame = Util.requestAnimFrame(this.drawLayer, this)
      }
      return this
    },
    _onLayerDidResize: function _onLayerDidResize (resizeEvent) {
      this._canvas.width = resizeEvent.newSize.x
      this._canvas.height = resizeEvent.newSize.y
    },
    _onLayerDidMove: function _onLayerDidMove () {
      var topLeft = this._map.containerPointToLayerPoint([0, 0])
      DomUtil.setPosition(this._canvas, topLeft)
      this.drawLayer()
    },
    _onLayerDidMoveStart: function _onLayerDidMoveStart () {this._hiddenLayer()},
    _hiddenLayer: function () {this._canvas.style.display = 'none'},
    _showLayer: function () {this._canvas.style.display = 'block'},
    getEvents: function getEvents () {
      var events = {
        resize: this._onLayerDidResize,
        moveend: this._onLayerDidMove,
        movestart: this._onLayerDidMoveStart
      }
      if (this._map.options.zoomAnimation &&
        Browser.any3d) {events.zoomanim = this._animateZoom}
      return events
    },
    onAdd: function onAdd (map) {
      this._map = map
      this._canvas = DomUtil.create('canvas', 'winfo-layer')
      this.tiles = {}
      var size = this._map.getSize()
      this._canvas.width = size.x
      this._canvas.height = size.y
      var animated = this._map.options.zoomAnimation && Browser.any3d
      DomUtil.addClass(this._canvas,
        'winfo-zoom-' + (animated ? 'animated' : 'hide'))
      this.options.pane.appendChild(this._canvas)
      map.on(this.getEvents(), this)
      var del = this._delegate || this
      del.onLayerDidMount && del.onLayerDidMount()
      this.needRedraw()
      var self = this
      setTimeout(function () {self._onLayerDidMove()}, 0)
    },
    onRemove: function onRemove (map) {
      var del = this._delegate || this
      del.onLayerWillUnmount && del.onLayerWillUnmount()
      this.options.pane.removeChild(this._canvas)
      map.off(this.getEvents(), this)
      this._canvas = null
    },
    addTo: function addTo (map) {
      map.addLayer(this)
      return this
    },
    drawLayer: function drawLayer () {
      var size = this._map.getSize()
      var bounds = this._map.getBounds()
      var zoom = this._map.getZoom()
      var center = this._map.options.crs.project(this._map.getCenter())
      var corner = this._map.options.crs.project(
        this._map.containerPointToLatLng(this._map.getSize()))
      var del = this._delegate || this
      var overlay = {
        layer: this,
        canvas: this._canvas,
        bounds: bounds,
        size: size,
        zoom: zoom,
        center: center,
        corner: corner
      }
      var that = this
      var params = { callback: function () {that._showLayer()} }
      del.onDrawLayer && del.onDrawLayer(overlay, params)
      this._frame = null
    },
    _animateZoom: function _animateZoom (e) {
      var scale = this._map.getZoomScale(e.zoom)
      var offset = Layer
        ? this._map._latLngToNewLayerPoint(
          this._map.getBounds().getNorthWest(), e.zoom, e.center)
        : this._map._getCenterOffset(e.center).
        _multiplyBy(-scale).
        subtract(this._map._getMapPanePos())
      DomUtil.setTransform(this._canvas, offset, scale)
    }
  })
  Control.Velocity = Control.extend({
    options: {
      position: 'bottomleft',
      emptyString: 'Unavailable',
      angleConvention: 'bearingCCW',
      speedUnit: 'm/s',
      onAdd: null,
      onRemove: null
    },
    onAdd: function onAdd (map) {
      this._container = DomUtil.create('div', 'winfo-control-velocity')
      DomEvent.disableClickPropagation(this._container)
      map.on('mousemove', this._onMouseMove, this)
      this._container.innerHTML = this.options.emptyString
      if (this.options.winfoVelocity.options.onAdd) this.options.winfoVelocity.options.onAdd()
      return this._container
    },
    onRemove: function onRemove (map) {
      map.off('mousemove', this._onMouseMove, this)
      if (this.options.winfoVelocity.options.onRemove) this.options.winfoVelocity.options.onRemove()
    },
    vectorToSpeed: function vectorToSpeed (uMs, vMs, unit) {
      var velocityAbs = Math.sqrt(Math.pow(uMs, 2) + Math.pow(vMs, 2))
      if (unit === 'k/h') {
        return this.meterSec2kilometerHour(velocityAbs)
      } else if (unit === 'kt') {
        return this.meterSec2Knots(velocityAbs)
      } else {return velocityAbs}
    },
    vectorToDegrees: function vectorToDegrees (uMs, vMs, angleConvention) {
      if (angleConvention.endsWith('CCW')) {
        vMs = vMs > 0
          ? vMs = -vMs
          : Math.abs(vMs)
      }
      var velocityAbs = Math.sqrt(Math.pow(uMs, 2) + Math.pow(vMs, 2))
      var velocityDir = Math.atan2(uMs / velocityAbs, vMs / velocityAbs)
      var velocityDirToDegrees = velocityDir * 180 / Math.PI + 180
      if (angleConvention === 'bearingCW' || angleConvention === 'meteoCCW') {
        velocityDirToDegrees += 180
        if (velocityDirToDegrees >= 360) velocityDirToDegrees -= 360
      }
      return velocityDirToDegrees
    },
    meterSec2Knots: function meterSec2Knots (meters) {return meters / 0.514},
    meterSec2kilometerHour: function meterSec2kilometerHour (meters) {
      return meters * 3.6
    },
    _onMouseMove: function _onMouseMove (e) {
      var self = this
      var pos = this.options.winfoVelocity._map.containerPointToLatLng(
        new Point(e.containerPoint.x, e.containerPoint.y))
      var gridValue = this.options.winfoVelocity._windy.interpolate(pos.lng,
        pos.lat, this.options.winfoVelocity._windy)
      var htmlOut = ''
      if (gridValue && !isNaN(gridValue[0]) && !isNaN(gridValue[1]) &&
        gridValue[2]) {
        htmlOut = '<strong>' + this.options.velocityType +
          ' Direction: </strong>' +
          self.vectorToDegrees(gridValue[0], gridValue[1],
            this.options.angleConvention).toFixed(2) + '°' + ', <strong>' +
          this.options.velocityType + ' Speed: </strong>' +
          self.vectorToSpeed(gridValue[0], gridValue[1],
            this.options.speedUnit).toFixed(2) + this.options.speedUnit
      } else {htmlOut = this.options.emptyString}
      self._container.innerHTML = htmlOut
    }
  })
  Map.mergeOptions({ positionControl: false })
  Map.addInitHook(function () {
    if (this.options.positionControl) {
      this.positionControl = new Control.MousePosition()
      this.addControl(this.positionControl)
    }
  })
  var VelocityLayer = Layer.extend({
    options: {
      displayValues: true,
      displayOptions: {
        velocityType: 'Velocity',
        position: 'bottomleft',
        emptyString: 'No velocity data'
      },
      data: null
    },
    _map: null,
    _canvasLayer: null,
    _windy: null,
    _context: null,
    _timer: 0,
    _mouseControl: null,
    initialize: function initialize (options) {setOptions(this, options)},
    onAdd: function onAdd (map) {
      this._paneName = this.options.paneName || 'overlayPane'
      var pane = map._panes.overlayPane
      if (map.getPane) {
        pane = map.getPane(this._paneName)
        if (!pane) {pane = map.createPane(this._paneName)}
      }
      this._canvasLayer = new FlowCanvasLayer({ pane: pane }).delegate(this)
      this._canvasLayer.addTo(map)
      this._map = map
    },
    onRemove: function onRemove (map) {this._destroyWind()},
    setData: function setData (data) {
      this.options.data = data
      if (this._windy) {
        this._windy.setData(data)
        this._clearAndRestart()
      }
      this.fire('load')
    },
    setOptions: function setOptions (options) {
      this.options = Object.assign(this.options, options)
      if (options.hasOwnProperty('displayOptions')) {
        this.options.displayOptions = Object.assign(this.options.displayOptions,
          options.displayOptions)
        this._initMouseHandler(true)
      }
      if (options.hasOwnProperty('data')) this.options.data = options.data
      if (this._windy) {
        this._windy.setOptions(options)
        if (options.hasOwnProperty('data')) this._windy.setData(options.data)
        this._clearAndRestart()
      }
      this.fire('load')
    },
    onDrawLayer: function onDrawLayer (overlay, params) {
      var self = this
      if (!this._windy) {
        this._initWindy(this)
        return
      }
      if (!this.options.data) {return}
      if (this._timer) clearTimeout(self._timer)
      this._timer = setTimeout(function () {
        self._startWindy()
        setTimeout(
          function () {params && params.callback && params.callback()},
          250)
      }, 700)
    },
    _startWindy: function _startWindy () {
      var bounds = this._map.getBounds()
      var size = this._map.getSize()
      this._windy.start([[0, 0], [size.x, size.y]], size.x, size.y, [
        [bounds._southWest.lng, bounds._southWest.lat],
        [bounds._northEast.lng, bounds._northEast.lat]
      ])
    },
    _initWindy: function _initWindy (self) {
      var options = Util.extend({
        canvas: self._canvasLayer._canvas,
        map: this._map
      }, self.options)
      this._windy = new VectorFlowLayer(options)
      this._context = this._canvasLayer._canvas.getContext('2d')
      this._canvasLayer._canvas.classList.add('velocity-overlay')
      this.onDrawLayer()
      this._map.on('dragstart', self._windy.stop)
      this._map.on('dragend', self._clearAndRestart)
      this._map.on('zoomstart', self._windy.stop)
      this._map.on('zoomend', self._clearAndRestart)
      this._map.on('resize', self._clearWind)
      this._initMouseHandler(false)
    },
    _initMouseHandler: function _initMouseHandler (voidPrevious) {
      if (voidPrevious) {
        this._map.removeControl(this._mouseControl)
        this._mouseControl = false
      }
      if (!this._mouseControl && this.options.displayValues) {
        var options = this.options.displayOptions || {}
        options['winfoVelocity'] = this
        this._mouseControl = new Control.Velocity(options).addTo(this._map)
      }
    },
    _clearAndRestart: function _clearAndRestart () {
      if (this._context) this._context.clearRect(0, 0, 3000, 3000)
      if (this._windy) this._startWindy()
    },
    _clearWind: function _clearWind () {
      if (this._windy) this._windy.stop()
      if (this._context) this._context.clearRect(0, 0, 3000, 3000)
    },
    _destroyWind: function _destroyWind () {
      if (this._timer) clearTimeout(this._timer)
      if (this._windy) this._windy.stop()
      if (this._context) this._context.clearRect(0, 0, 3000, 3000)
      if (this._mouseControl) this._map.removeControl(this._mouseControl)
      this._mouseControl = null
      this._windy = null
      this._map.removeLayer(this._canvasLayer)
    }
  })

  class TrackPlayBack extends CanvasLayer {
    constructor (options) {
      super()
      this.options = {
        data: [],
        speed: 18,
        speedStep: 0.5,
        maxSpeed: 65,
        playTrackStyle: {
          useImage: false,
          imageUrl: '',
          imageSize: [20, 20],
          radius: 5,
          strokeStyle: '#000',
          fillStyle: '#fff',
          openToolTip: true,//跟随标签，显示轨迹船名
          toolTipOptions: {
            offset: [0, 0],
            direction: 'auto',
            permanent: true
          },
          toolTipTemplate: false
        },
        neverTrackStyle: {
          showLine: false,
          showPoint: false,
          lineStrokeStyle: 'rgba(153,50,204,0.2)',
          lineLineWidth: 2,
          lineDashArray: [2, 2],
          pointStrokeStyle: 'rgba(0,128,0,0.5)',
          pointLineWidth: 1,
          pointFillStyle: 'rgba(255,255,255,0.5)',
          pointRadius: 2
        },
        aleadyTrackStyle: {
          showLine: true,
          showPoint: true,
          lineStrokeStyle: 'rgba(153,50,204,0.6)',
          lineLineWidth: 4,
          lineDashArray: [0, 0],
          pointStrokeStyle: 'rgba(0,128,0,1)',
          pointLineWidth: 1,
          pointFillStyle: 'rgba(255,255,255,1)',
          pointRadius: 3
        },
        mouseOverPointToolTip: {
          show: true,
          template: false,
          options: {
            offset: [0, 0],
            direction: 'auto',
            permanent: false
          }
        },
        events: {
          formatDrawStyle: false,
          playProgressChange: false
        },
        idKey: 'MMSI',
        zIndex: 112,
        className: 'trackPlayBack'
      },
        this.timeLine = [0, 0]
      this.playProcess = 0
      this._intervalID = null
      this._lastFpsUpdateTime = 0
      this._curPlayTime = 0
      this._playTrackToolTip = {}
      this._pointToolTip = null
      this._currFpsDrawData = []
      this._currDrawTrackIndex = 0
      extendOptions(this.options, options)
      return this
    }

    addTo (map) {
      this.requestTrackImg().then(res => {
        this.add(map, {
          zIndex: this.options.zIndex,
          className: this.options.className
        })
        this._initTrackPlayStatus()
      })
      return this
    }

    onRemove () {
      this.remove()
      this.timeLine = [0, 0]
      this.playProcess = 0
      this._intervalID = null
      this._lastFpsUpdateTime = 0
      this._curPlayTime = 0
      this._currFpsDrawData = []
      this.tracks = []
      this.options.data = []
      this._removeAllToopTip()
      return null
    }

    _onMouseMove (e) {
      this._openPointToolTip(e)
    }

    reDraw (e) {
      this._refreshCanvas(e)
    }

    _onMouseClick (e) {
      this._openPointToolTip(e)
    }

    requestTrackImg () {
      return new Promise(resolve => {
        if (this.options.playTrackStyle.useImage) {
          const img = new Image()
          img.onload = () => {this._targetImg = img}
          img.src = this.options.playTrackStyle.imageUrl
          resolve()
        } else {
          resolve()
        }
      })
    }

    setMutiColor (colorArr) {
      this.options.aleadyTrackStyle.lineStrokeStyle = colorArr
      this.options.aleadyTrackStyle.pointStrokeStyle = colorArr
      this.options.playTrackStyle.strokeStyle = colorArr
    }

    _initTracks () {
      this.tracks = []
      let data = this.options.data
      if (!data.length) return
      this.tracks = data.map(d => new Track(d))
    }

    _initTimeLine () {
      this.timeLine = [0, 0]
      if (!this.tracks.length) return
      let timeLine = []
      this.tracks.map(
        d => {timeLine.push(d.getFirstPoint().time, d.getEndPoint().time)})
      this.timeLine = [Math.min(...timeLine), Math.max(...timeLine)]
      this._curPlayTime = this.timeLine[0]
      this.progress = 0
      this.drawTracksByTime()
    }

    _openPointToolTip (event) {
      if (this._pointToolTip) {
        this._map.removeLayer(this._pointToolTip)
      }
      if (!this._currFpsDrawData.length) return
      let eventPoint = event.layerPoint
      let tooltipOptions = this.options.mouseOverPointToolTip
      for (let i = 0; i < this._currFpsDrawData.length; i++) {
        let d = this._currFpsDrawData[i]
        let point = this._canvas.getLayerPoint([d.lat, d.lng])
        point = new Point(point.x, point.y)
        if (point.distanceTo(eventPoint) <= (d.radius + 2)) {
          this._pointToolTip = new Tooltip(tooltipOptions.options)
          this._pointToolTip.setLatLng([d.lat, d.lng])
          this._pointToolTip.addTo(this._map)
          let trackContent = tooltipOptions.template ? tooltipOptions.template(
            d) : this._defaultToolTipTemplate(d)
          this._pointToolTip.setContent(trackContent)
          break
        }
      }
    }

    _initTrackPlayStatus () {
      this._initTracks()
      this._initTimeLine()
      this._removeAllToopTip()
    }

    _refreshCanvas () {
      this._currFpsDrawData = []
      this._removeAllToopTip()
      if (!this._canvas) return
      this._canvas._clearContent()
      this._lastFpsUpdateTime = +new Date()
      if (!this._intervalID) {this.drawTracksByTime()}
    }

    playStart () {
      if (this._intervalID) return
      this._lastFpsUpdateTime = +new Date()
      this._intervalID = Util.requestAnimFrame(this._tick, this)
    }

    playStop () {this._intervalID = null}

    rePlay () {
      this._curPlayTime = this.timeLine[0]
      this._intervalID = null
      this.playProcess = 0
      this.playStart()
    }

    speedUp () {
      this.options.speed += this.options.speedStep
      if (this.options.speed >
        this.options.maxSpeed) {this.options.speed = this.options.maxSpeed}
    }

    speedDown () {
      this.options.speed -= this.options.speedStep
      if (this.options.speed < 0) {this.options.speed = 0}
    }

    JumpTime (time) {
      this._intervalID = null
      this._curPlayTime = time
      if (time >= this.timeLine[0] && time <= this.timeLine[1]) {
        setTimeout(() => {
          this._currFpsDrawData = []
          this.drawTracksByTime()
          this._curcalPlayProgress()
        }, 10)
      }
    }

    setPlayData (data) {
      this.playStop()
      this.options.data = data
      this._initTrackPlayStatus()
      this._refreshCanvas()
      return this
    }

    addPlayData (data) {
      this.playStop()
      this.options.data.push(data)
      this._initTrackPlayStatus()
      this._refreshCanvas()
      return this
    }

    removePlayData () {
      this.playStop()
      this.options.data = []
      this.timeLine = [0, 0]
      this._intervalID = null
      this.currPlayTime = 0
      this._refreshCanvas()
      return this
    }

    setOptions (options) {
      this.playStop()
      extendOptions(this.options, options)
      if (options.hasOwnProperty('data')) {
        this._initTrackPlayStatus()
        this._refreshCanvas()
      }
      return this
    }

    _curcalPlayProgress () {
      let section = this.timeLine[1] - this.timeLine[0]
      section = section / 100
      let progress = (this._curPlayTime - this.timeLine[0]) / section
      if (progress > 100) {progress = 100}
      this.playProcess = progress
      this.options.events.playProgressChange &&
      this.options.events.playProgressChange(this.playProcess)
    }

    _tick () {
      let now = +new Date()
      let fpstime = this._caculatefpsTime(now)
      let isPause = false
      let stepTime = fpstime * Math.pow(2, this.options.speed - 1)
      this._curPlayTime += stepTime
      this._currFpsDrawData = []
      if (this._curPlayTime > this.timeLine[1]) {
        this._curPlayTime = this.timeLine[1]
        isPause = true
        this._intervalID = null
      }
      this.drawTracksByTime()
      this._curcalPlayProgress()
      if (!isPause && this._intervalID) {
        this._intervalID = Util.requestAnimFrame(this._tick, this)
      }
    }

    drawTracksByTime () {
      if (!this._canvas) return
      this._canvas._clearContent()
      this.tracks.map((d, i) => {
        this._currDrawTrackIndex = i
        let path = d.getTrackPointsBeforeCurPlayTime(this._curPlayTime)
        this._drawTrack(path.neverTrackPoints, 'never')
        this._drawTrack(path.alreadyTrackPoints, 'aleady')
      })
    }

    _drawTrack (path, type) {
      let drawPath = []
      let idKey = null
      path.map((item) => {
        let pointSrceen = this._canvas.getLayerPoint([item.lat, item.lng])
        drawPath.push(pointSrceen)
        item[this.options.idKey] && (idKey = item[this.options.idKey])
      })
      this._drawTrackLine({
        drawPath,
        type,
        idKey
      })
      path.map((item, i) => {
        let pointSrceen = this._canvas.getLayerPoint([item.lat, item.lng])
        this._drawTrackPoint({
          path: [pointSrceen],
          type,
          sourceData: item
        })
        if (type === 'aleady' && i === path.length - 1) {
          this._drawTrackPlayHead({
            pointSrceen,
            item
          })
          this._drawTrackPlayHeadToolTip(item)
        }
      })
    }

    _drawTrackPlayHead ({ pointSrceen, item }) {
      let playTrackStyle = this.options.playTrackStyle
      if (playTrackStyle.useImage) {
        let imageOptions = {
          path: [pointSrceen],
          icon: this._targetImg,
          width: playTrackStyle.imageSize[0],
          height: playTrackStyle.imageSize[1],
          rotate: item.HX,
          sourceData: item
        }
        this.options.events.formatDrawStyle &&
        this.options.events.formatDrawStyle({
          style: imageOptions,
          drawType: 'image'
        })
        CanvasDrawTool.drawImage(this._canvas._ctx, imageOptions)
      } else {
        let strokeStyle = playTrackStyle.strokeStyle
        typeof strokeStyle === 'object' &&
        (strokeStyle = strokeStyle[this._currDrawTrackIndex])
        let threeOptions = {
          strokeStyle: strokeStyle,
          fillStyle: playTrackStyle.fillStyle,
          rotateCenter: pointSrceen,
          path: [pointSrceen],
          rotate: item.HX,
          sourceData: item
        }
        this.options.events.formatDrawStyle &&
        this.options.events.formatDrawStyle({
          style: threeOptions,
          drawType: 'icon'
        })
        CanvasDrawTool.drawThreeShip(this._canvas._ctx, threeOptions)
      }
    }

    _drawTrackPlayHeadToolTip (item) {
      let playTrackStyle = this.options.playTrackStyle
      if (!playTrackStyle.openToolTip) return
      let content = this.options.playTrackStyle.toolTipTemplate
        ? this.options.playTrackStyle.toolTipTemplate()
        : item.nameCn || item.nameEn || item[this.options.idKey].toString()
      let color = this.options.aleadyTrackStyle.lineStrokeStyle[this._currDrawTrackIndex] ||
        this.options.aleadyTrackStyle.lineStrokeStyle
      CanvasDrawTool.drawShapLabel(this._canvas._ctx, {
        pointingColor: color,
        pointingWidth: 1,
        boxColor: color,
        boxWidth: 2,
        text: content,
        path: [this._canvas.getLayerPoint([item.lat, item.lng])]
      })
    }

    _defaultToolTipTemplate (item) {
      let html = ''
      for (let key in item) {
        if (item.hasOwnProperty(key) && key !=
          'isOrigin') {html += `${key}:${item[key]}<br/>`}
      }
      return html
    }

    _removeAllToopTip () {
      for (let key in this._playTrackToolTip) {
        if (this._playTrackToolTip.hasOwnProperty(key) &&
          this._playTrackToolTip[key]) this._map.removeLayer(
          this._playTrackToolTip[key])
      }
      if (this._pointToolTip) this._map.removeLayer(this._pointToolTip)
    }

    _drawTrackLine ({ drawPath, type, idKey }) {
      let styleOption = extend({}, this.options[type + 'TrackStyle'])
      if (!styleOption.showLine) return
      let lineStrokeStyle = styleOption.lineStrokeStyle
      typeof lineStrokeStyle === 'object' &&
      (lineStrokeStyle = lineStrokeStyle[this._currDrawTrackIndex])
      let style = {
        strokeStyle: lineStrokeStyle,
        lineWidth: styleOption.lineLineWidth,
        dashArray: styleOption.lineDashArray,
        path: drawPath,
        sourceData: { [this.options.idKey]: idKey }
      }
      this.options.events.formatDrawStyle &&
      this.options.events.formatDrawStyle({
        style,
        drawType: 'polyline'
      })
      CanvasDrawTool.drawLine(this._canvas._ctx, style)
    }

    _drawTrackPoint ({ path, type, sourceData }) {
      let styleOption = extend({}, this.options[type + 'TrackStyle'])
      if (!styleOption.showPoint) return
      if (this.options.mouseOverPointToolTip.show) {
        let currDrawFps = extend({ radius: styleOption.pointRadius },
          sourceData)
        this._currFpsDrawData.push(currDrawFps)
      }
      let strokeStyle = styleOption.pointStrokeStyle
      typeof strokeStyle === 'object' &&
      (strokeStyle = strokeStyle[this._currDrawTrackIndex])
      let style = {
        strokeStyle: strokeStyle,
        lineWidth: styleOption.pointLineWidth,
        fillStyle: styleOption.pointFillStyle,
        radius: styleOption.pointRadius,
        path: path,
        sourceData: sourceData
      }
      this.options.events.formatDrawStyle &&
      this.options.events.formatDrawStyle({
        style,
        drawType: 'point'
      })
      CanvasDrawTool.drawPoint(this._canvas._ctx, style)
    }

    _caculatefpsTime (now) {
      let time = 0
      if (this._lastFpsUpdateTime !== 0) {time = now - this._lastFpsUpdateTime}
      this._lastFpsUpdateTime = now
      time = time / 1000
      return time
    }
  }

  let Track = Class.extend({
    options: {},
    _timeTick: {},
    _trackPoints: [],
    initialize: function (data) {
      data.map((d, i) => {
        if (i === 0) {d.isOrigin = 'start'} else if (i ===
          (data.length - 1)) {d.isOrigin = 'end'} else {d.isOrigin = true}
      })
      this._trackPoints = data
      this.updateData()
      return this
    },
    updateData: function () {this._sortTrackPointByTime()},
    _sortTrackPointByTime: function () {
      this._timeTick = {}
      this._trackPoints.map(d => {this._timeTick[d.time] = d})
      this._trackPoints = Object.values(this._timeTick)
    },
    getFirstPoint: function () {return this._trackPoints[0]},
    getEndPoint: function () {
      return this._trackPoints[this._trackPoints.length - 1]
    },
    getTrackPointsBeforeCurPlayTime: function (currPlayTime) {
      let alreadyTrackPoints = []
      let neverTrackPoints = []
      if (!this._trackPoints.length) return []
      let section = []
      this._trackPoints.map((d, i) => {
        if (d.time < currPlayTime) {
          alreadyTrackPoints.push(d)
          section[0] = d.time
          if (i !== this._trackPoints.length -
            1) {section[1] = this._trackPoints[i + 1].time}
        }
        if (d.time > currPlayTime) {neverTrackPoints.push(d)}
      })
      if (alreadyTrackPoints.length === this._trackPoints.length) return {
        alreadyTrackPoints,
        neverTrackPoints
      }
      let endPoint = this._getLastPointByInterpolation(currPlayTime, section)
      if (endPoint) {
        alreadyTrackPoints.push(endPoint)
        neverTrackPoints.unshift(endPoint)
      }
      return {
        alreadyTrackPoints,
        neverTrackPoints
      }
    },
    _getLastPointByInterpolation: function (currPlayTime, section) {
      if (!section.length) return
      let p0 = this._timeTick[section[0]]
      let p1 = this._timeTick[section[1]]
      let startPoint = new Point(p0.lng, p0.lat)
      let endPoint = new Point(p1.lng, p1.lat)
      let length = startPoint.distanceTo(endPoint)
      if (length <= 0) return p1
      let v = length / (p1.time - p0.time)
      let sinx = (endPoint.y - startPoint.y) / length
      let cosx = (endPoint.x - startPoint.x) / length
      let step = v * (currPlayTime - p0.time)
      let x = startPoint.x + step * cosx
      let y = startPoint.y + step * sinx

      let dir = Util.getTwoPointDir(p0, p1)
      let lastPoint = extend({}, p0)
      extend(lastPoint, {
        lng: x,
        lat: y,
        HX: dir,
        isOrigin: false,
        time: currPlayTime
      })
      return lastPoint
    }
  })

  class TrackBack extends CanvasLayer {
    constructor (options) {
      super()
      this.options = {
        zIndex: 112,
        className: 'trackBack',
        data: [],
        SparseGranularity: 60,
        trackPointStyle: {
          show: true,
          fillStyle: 'rgba(255,255,255,1)',
          strokeStyle: 'rgba(116,28,123,1)',
          lineWidth: 2,
          radius: 3
        },
        trackLineStyle: {
          strokeStyle: 'rgba(116,28,123,1)',
          lineWidth: 1,
          dashArray: [0, 0]
        },
        trackHXStyle: {
          show: true,
          fillStyle: 'rgba(116,28,123,1)',
          strokeStyle: 'rgba(116,28,123,1)',
          lineWidth: 2
        },
        timeLableStyle: {
          show: true,
          lableFont: '300 14px Arial',
          lableTxtColor: 'rgba(0,0,0,1)',
          labelToBoxLineColor: 'rgba(116,28,123,1)',
          labelToBoxLineWidth: 2,
          lableBoxColor: 'rgba(116,28,123,1)',
          lableBoxfillColor: 'rgba(255,255,255,0.6)',
          lableBoxlineWidth: 1
        },
        trackStartStyle: {
          useImage: true,
          url: '',
          size: [16, 16]
        },
        trackEndStyle: {
          useImage: true,
          url: '',
          size: [16, 16]
        },
        trackStopStyle: {
          value: 0.1,
          useImage: true,
          url: '',
          size: [24, 34]
        },
        trackSlowStyle: {
          value: 5,
          useImage: true,
          url: '',
          size: [24, 34]
        },
        mousePointToolTip: {
          mode: 'click',
          show: true,
          template: false,
          options: {
            offset: [0, 0],
            direction: 'auto',
            permanent: false
          }
        },
        events: { formatDrawStyle: false },
        idKey: 'MMSI'
      }
      this._nowDrawTrackIndex = 0
      this._pointToolTip = null
      this.setOptions(options)
      return this
    }

    addTo (map) {
      this.initImage().then(res => {
        this.add(map, {
          zIndex: this.options.zIndex,
          className: this.options.className
        })
        this._drawAllTrackBack()
      })
      return this
    }

    onRemove () {
      this.remove()
      this.options.data = []
      return null
    }

    setMutiColor (colorArr) {
      this.options.trackPointStyle.strokeStyle = colorArr
      this.options.trackLineStyle.strokeStyle = colorArr
      this.options.trackHXStyle.strokeStyle = colorArr
      this.options.trackHXStyle.fillStyle = colorArr
      this.options.timeLableStyle.labelToBoxLineColor = colorArr
      this.options.timeLableStyle.lableBoxColor = colorArr
    }

    initImage (type) {
      return new Promise(resolve => {
        let request = []
        let reuqestImgFile = ['Start', 'End', 'Stop', 'Slow']
        reuqestImgFile.map(d => {
          request.push(
            CanvasDrawTool.loadImage(this.options[`track${d}Style`].url))
        })
        Promise.all(request).
        then(res => {
          reuqestImgFile.map((d, i) => {this[`_${d}Img`] = res[i]})
          resolve()
        })
      })
    }

    resetData (data) {
      this.options.data = data
      this._refreshCanvas()
    }

    _onMouseMove (e) {
      if (!this.options.mousePointToolTip.show ||
        this.options.mousePointToolTip.mode !== 'mousemove') return
      this._openPointToolTip(e)

    }

    reDraw () {
      this._refreshCanvas()
    }

    _onMouseClick (e) {
      if (!this.options.mousePointToolTip.show ||
        this.options.mousePointToolTip.mode !== 'click') return
      this._openPointToolTip(e)
    }

    _drawAllTrackBack () {
      if (!this._canvas) return
      this._canvas._clearContent()
      if (!this.options.data.length) return
      this.options.data.map((d, index) => {
        this._nowDrawTrackIndex = index
        this._drawTrackBack(d)
      })
    }

    _getDrawStyle () {
      let lineStyle = extend({}, this.options.trackLineStyle)
      return {
        lineStrokeStyle: lineStyle.strokeStyle.length
          ? lineStyle.strokeStyle[this._nowDrawTrackIndex]
          : lineStyle.strokeStyle,
        lineLineWidth: lineStyle.lineWidth,
        lineDashArray: lineStyle.dashArray
      }
    }

    _drawTrackBack (trackbackdata) {
      if (!trackbackdata.length) return
      let id = trackbackdata[0][this.options.idKey]
      let style = this._getDrawStyle()
      let beforePoint = null
      let beforeLatLng = null
      let dataLength = trackbackdata.length
      let trackPath = []
      for (let i = 0; i < dataLength; i++) {
        let data = trackbackdata[i]
        let screenPoint = this._canvas.getLayerPoint([data.lat, data.lng])
        if (i !== 0 && i !== (dataLength - 1)) {
          let beforeScreenLength = screenPoint.distanceTo(beforePoint)
          if (beforeScreenLength >= this.options.SparseGranularity) {
            trackPath.push(screenPoint)
            this._canvas._ctx.globalCompositeOperation = 'destination-over'
            // 绘制属性图标
            this._drawNaviStausIcon({
              ...screenPoint,
              sourceData: data
            })
            // 绘制轨迹点
            this.options.trackPointStyle.show && this._drawTrackPoint({
              ...screenPoint,
              sourceData: data
            })
            // 绘制时间标签
            this._drawTimeLabel({
              ...screenPoint,
              sourceData: data
            })
            this._canvas._ctx.globalCompositeOperation = 'destination-over'
            this._drawTrackHX({
              beforeGeo: beforeLatLng,
              thisGeo: data,
              MMSI: data[id]
            })
            beforePoint = screenPoint
            beforeLatLng = new LatLng(data.lat, data.lng)
          }
          continue
        }
        this._canvas._ctx.globalCompositeOperation = 'source-over'
        if (i !== 0) {
          this._drawTrackHX({
            beforeGeo: beforeLatLng,
            thisGeo: data,
            MMSI: data[id]
          })
        }
        this._drawTimeLabel({
          ...screenPoint,
          sourceData: data
        })
        beforePoint = screenPoint
        beforeLatLng = new LatLng(data.lat, data.lng)
        trackPath.push(screenPoint)
        // 绘制第一个点/最后一个点的图标
        this.drawStartAndEndPoint(i === 0 ? 'Start' : 'End', screenPoint, data)
      }
      this.drawLine(trackPath, style)
    }

    drawStartAndEndPoint (type, screenPoint, sourceData) {
      let trackOption = this.options[`track${type}Style`]
      if (trackOption.useImage) {
        this._drawImage({
          ...screenPoint,
          img: this[`_${type}Img`],
          size: trackOption.size
        })
      } else {
        this.options.trackPointStyle.show && this._drawTrackPoint({
          ...screenPoint,
          sourceData
        })
      }
    }

    drawLine (trackPath, style) {
      let drawLineData = {
        strokeStyle: style.lineStrokeStyle,
        lineWidth: style.lineLineWidth,
        dashArray: style.lineDashArray,
        path: trackPath
      }
      this.options.events.formatDrawStyle &&
      this.options.events.formatDrawStyle(drawLineData)
      this._canvas._ctx.globalCompositeOperation = 'destination-over'
      CanvasDrawTool.drawLine(this._canvas._ctx, drawLineData)
    }

    _drawImage ({ x, y, img, size }) {
      let imageOptions = {
        path: [
          {
            x: x,
            y: y
          }
        ],
        icon: img,
        width: size[0],
        height: size[1],
        rotate: 0
      }
      CanvasDrawTool.drawImage(this._canvas._ctx, imageOptions)
    }

    _drawTimeLabel ({ x, y, sourceData }) {
      if (!this.options.timeLableStyle.show) return
      let timeLableStyle = this.options.timeLableStyle
      let labelToBoxLineColor = typeof timeLableStyle.labelToBoxLineColor ===
      'object'
        ? timeLableStyle.labelToBoxLineColor[this._nowDrawTrackIndex]
        : timeLableStyle.labelToBoxLineColor
      let lableBoxColor = typeof timeLableStyle.lableBoxColor === 'object'
        ? timeLableStyle.lableBoxColor[this._nowDrawTrackIndex]
        : timeLableStyle.lableBoxColor
      CanvasDrawTool.drawShapLabel(this._canvas._ctx, {
        pointingColor: labelToBoxLineColor,
        pointingWidth: timeLableStyle.labelToBoxLineWidth,
        boxColor: lableBoxColor,
        boxWidth: timeLableStyle.lableBoxlineWidth,
        boxFill: timeLableStyle.lableBoxfillColor,
        text: Util.getDateFomat('Y-M-D H:m:s', sourceData.time),
        textColor: timeLableStyle.lableTxtColor,
        path: [
          {
            x: x + this.options.trackPointStyle.radius,
            y: y - this.options.trackPointStyle.radius / 2
          }
        ]
      })
    }

    _drawTrackHX ({ beforeGeo, thisGeo, MMSI }) {
      let middleLat = (beforeGeo.lat + thisGeo.lat) / 2
      let middleLng = (beforeGeo.lng + thisGeo.lng) / 2
      let HX = Util.getTwoPointDir(beforeGeo, thisGeo)
      let middleScreen = this._canvas.getLayerPoint(
        new LatLng(middleLat, middleLng))
      let hxStyle = extend({}, this.options.trackHXStyle)
      if (typeof hxStyle.fillStyle ===
        'object') hxStyle.fillStyle = hxStyle.fillStyle[this._nowDrawTrackIndex]
      if (typeof hxStyle.strokeStyle ===
        'object') hxStyle.strokeStyle = hxStyle.strokeStyle[this._nowDrawTrackIndex]
      hxStyle.rotateCenter = middleScreen
      hxStyle.path = [middleScreen]
      hxStyle.rotate = HX
      hxStyle.type = 'HX'
      hxStyle.sourceData = { [this.options.idKey]: MMSI }
      this.options.events.formatDrawStyle &&
      this.options.events.formatDrawStyle(hxStyle)
      this.options.trackHXStyle.show &&
      CanvasDrawTool.drawArrow(this._canvas._ctx, hxStyle)
    }

    _drawTrackPoint ({ x, y, sourceData }) {
      let pointStyle = extend({}, this.options.trackPointStyle)
      if (typeof pointStyle.strokeStyle ===
        'object') pointStyle.strokeStyle = pointStyle.strokeStyle[this._nowDrawTrackIndex]
      pointStyle.sourceData = sourceData
      pointStyle.type = 'point'
      pointStyle.path = [
        {
          x,
          y
        }
      ]
      this.options.events.formatDrawStyle &&
      this.options.events.formatDrawStyle(pointStyle)
      CanvasDrawTool.drawPoint(this._canvas._ctx, pointStyle)
    }

    _drawNaviStausIcon ({ x, y, sourceData }) {
      const trackStopStyle = this.options.trackStopStyle
      const trackSlowStyle = this.options.trackSlowStyle
      let img = null
      let size = trackSlowStyle.size
      if (trackStopStyle.useImage && sourceData.HS <= trackStopStyle.value) {
        size = trackStopStyle.size
        img = this._StopImg
      } else if (trackSlowStyle.useImage && sourceData.HS <=
        trackSlowStyle.value) {img = this._SlowImg}
      y = y - size[1] / 2
      img && this._drawImage({
        x,
        y,
        img,
        size
      })
    }

    _openPointToolTip (event) {
      if (this._pointToolTip) {
        this._map.removeLayer(this._pointToolTip)
      }
      let trackBackData = this.options.data
      if (!trackBackData.length) return
      let eventPoint = event.layerPoint
      let tooltipOptions = this.options.mousePointToolTip
      MultipleStrips:for (let i = 0; i < trackBackData.length; i++) {
        let list = trackBackData[i]
        for (let item = 0; item < list.length; item++) {
          let d = list[item]
          let point = this._canvas.getLayerPoint([d.lat, d.lng])
          if (point.distanceTo(eventPoint) <=
            (this.options.trackPointStyle.radius + 2)) {
            this._pointToolTip = new Tooltip(tooltipOptions.options)
            this._pointToolTip.setLatLng([d.lat, d.lng])
            this._pointToolTip.addTo(this._map)
            let trackContent = tooltipOptions.template
              ? tooltipOptions.template(d)
              : this._defaultToolTipTemplate(d)
            this._pointToolTip.setContent(trackContent)
            break MultipleStrips
          }
        }
      }
    }

    _defaultToolTipTemplate (data) {
      let lat = Util.conversionDegreeAndGeo.GeoToLatDegree(data.lat)
      let lon = Util.conversionDegreeAndGeo.GeoToLngDegree(data.lng)
      return `mmsi:${data.MMSI}<br/>纬度:${lat[0]}°${lat[1]}′${lat[2]}″${lat[3]}<br/>经度:${lon[0]}°${lon[1]}′${lon[2]}″${lon[3]}<br/>航速:${data.HS.toFixed(
        2)}节<br/>航向:${data.HX.toFixed(2)}°`
    }

    _refreshCanvas () {
      if (!this._canvas) return
      this._drawAllTrackBack()
    }
  }

  var Draw = {
    version: '1.0.4',
    drawLocal: {
      circle: {
        tooltip: { start: '单击地图并拖动鼠标' },
        radius: '半径'
      },
      circlemarker: { tooltip: { start: '单机左键并拖动鼠标' } },
      marker: { tooltip: { start: '单击鼠标左键添加图标' } },
      polygon: {
        tooltip: {
          start: '单击鼠标左键开始绘制多边形',
          cont: '单击鼠标左键继续绘制多边形',
          end: '双击结束绘制'
        }
      },
      polyline: {
        error: '<strong>错误:</strong> 形状边缘不能交叉',
        tooltip: {
          start: '单击鼠标左键开始绘制线条',
          cont: '单击鼠标左键继续绘制线条',
          end: '双击结束绘制'
        }
      },
      rectangle: { tooltip: { start: '单机左键并拖动鼠标' } },
      simpleshape: { tooltip: { end: '松开鼠标完成绘制' } }
    },
    Event: {
      CREATED: 'draw:created',
      EDITED: 'draw:edited',
      DELETED: 'draw:deleted',
      DRAWSTART: 'draw:drawstart',
      DRAWSTOP: 'draw:drawstop',
      DRAWVERTEX: 'draw:drawvertex',
      EDITSTART: 'draw:editstart',
      EDITMOVE: 'draw:editmove',
      EDITRESIZE: 'draw:editresize',
      EDITVERTEX: 'draw:editvertex',
      EDITSTOP: 'draw:editstop',
      DELETESTART: 'draw:deletestart',
      DELETESTOP: 'draw:deletestop',
      MARKERCONTEXT: 'draw:markercontext'
    },
    Feature: null,
    Polyline: null,
    Polygon: null,
    SimpleShape: null,
    Rectangle: null,
    Marker: null,
    CircleMarker: null,
    Circle: null
  }
  Draw.Feature = Handler.extend({
    editing: null,
    initialize: function (map, options) {
      this._map = map
      this._container = map._container
      this._overlayPane = map._panes.overlayPane
      this._popupPane = map._panes.popupPane
      this._layer = null
      if (options && options.shapeOptions) {
        options.shapeOptions = Util.extend({}, this.options.shapeOptions,
          options.shapeOptions)
      }
      setOptions(this, options)
      Draw.Feature.include(Evented.prototype)
    },
    initEvents: function () {
      this.options.events && this._layer.on(this.options.events)
    },
    offEvents: function (events) {this._layer.off(events)},
    enable: function () {
      if (this._enabled) {return}
      Handler.prototype.enable.call(this)
      this.fire('enabled', { handler: this.type })
      this._map.fire(Draw.Event.DRAWSTART, { layerType: this.type })
      return this
    },
    disable: function () {
      if (!this._enabled) {return}
      Handler.prototype.disable.call(this)
      this._map.fire(Draw.Event.DRAWSTOP, {
        layerType: this.type,
        sourceId: this._winfo_id,
        sourceData: this
      })
      this.fire('disabled', { handler: this.type })
    },
    edit: function () {
      this.editing.enable()
      return this._layer
    },
    disEdit: function () {
      this.editing.disable()
      return this._layer
    },
    addHooks: function () {
      var map = this._map
      if (map) {
        DomUtil.disableTextSelection()
        map.getContainer().focus()
        this._tooltip = new Draw.Tooltip(this._map)
        DomEvent.on(this._container, 'keyup', this._cancelDrawing, this)
      }
    },
    removeHooks: function () {
      if (this._map) {
        DomUtil.enableTextSelection()
        this._tooltip.dispose()
        this._tooltip = null
        DomEvent.off(this._container, 'keyup', this._cancelDrawing, this)
      }
    },
    setOptions: function (options) {setOptions(this, options)},
    _fireCreatedEvent: function (layer) {
      this._layer = layer
      this.editing = layer.editing
      this.initEvents()
      this.options.editable && this.editing.enable()
      this._map.addLayer(layer)
      this._map.fire(Draw.Event.CREATED, {
        layer: layer,
        layerType: this.type
      })
    },
    _cancelDrawing: function (e) {
      if (e.keyCode === 27) {
        this._map.fire('draw:canceled', { layerType: this.type })
        this.disable()
      }
    },
    remove: function () {this._map.removeLayer(this._layer)}
  })
  Draw.Polyline = Draw.Feature.extend({
    statics: { TYPE: 'polyline' },
    Poly: Polyline,
    options: {
      events: {},
      allowIntersection: true,
      repeatMode: false,
      drawError: {
        color: '#b00b00',
        timeout: 2500
      },
      icon: new DivIcon({
        iconSize: new Point(8, 8),
        className: 'winfo-div-icon winfo-editing-icon'
      }),
      touchIcon: new DivIcon({
        iconSize: new Point(20, 20),
        className: 'winfo-div-icon winfo-editing-icon winfo-touch-icon'
      }),
      guidelineDistance: 20,
      maxGuideLineLength: 4000,
      shapeOptions: {
        stroke: true,
        color: '#3388ff',
        weight: 4,
        opacity: 0.5,
        fill: false,
        clickable: true
      },
      metric: true,
      feet: false,
      nautic: false,
      showLength: true,
      zIndexOffset: 2000,
      factor: 1,
      maxPoints: 0
    },
    initialize: function (options) {
      if (Browser.touch) {this.options.icon = this.options.touchIcon}
      this.options.drawError.message = Draw.drawLocal.polyline.error
      extendOptions(this.options, options)
      this.type = Draw.Polyline.TYPE
      return this
    },
    addTo: function (map) {
      this._map = map
      Draw.Feature.prototype.initialize.call(this, map, this.options)
      return this
    },
    addHooks: function () {
      Draw.Feature.prototype.addHooks.call(this)
      if (this._map) {
        this._markers = []
        this._markerGroup = new LayerGroup()
        this._map.addLayer(this._markerGroup)
        this._poly = new Polyline([], this.options.shapeOptions)
        this._tooltip.updateContent(this._getTooltipText())
        if (!this._mouseMarker) {
          this._mouseMarker = new Marker(this._map.getCenter(), {
            icon: new DivIcon({
              className: 'winfo-mouse-marker',
              iconAnchor: [20, 20],
              iconSize: [40, 40]
            }),
            opacity: 0,
            zIndexOffset: this.options.zIndexOffset
          })
        }
        this._mouseMarker.on('mouseout', this._onMouseOut, this).
        on('mousemove', this._onMouseMove, this).
        on('mousedown', this._onMouseDown, this).
        on('mouseup', this._onMouseUp, this).
        addTo(this._map)
        this._map.on('mouseup', this._onMouseUp, this).
        on('mousemove', this._onMouseMove, this).
        on('zoomlevelschange', this._onZoomEnd, this).
        on('touchstart', this._onTouch, this).
        on('zoomend', this._onZoomEnd, this)
      }
    },
    removeHooks: function () {
      Draw.Feature.prototype.removeHooks.call(this)
      this._clearHideErrorTimeout()
      this._cleanUpShape()
      this._map.removeLayer(this._markerGroup)
      delete this._markerGroup
      delete this._markers
      this._map.removeLayer(this._poly)
      delete this._poly
      this._mouseMarker.off('mousedown', this._onMouseDown, this).
      off('mouseout', this._onMouseOut, this).
      off('mouseup', this._onMouseUp, this).
      off('mousemove', this._onMouseMove, this)
      this._map.removeLayer(this._mouseMarker)
      delete this._mouseMarker
      this._clearGuides()
      this._map.off('mouseup', this._onMouseUp, this).
      off('mousemove', this._onMouseMove, this).
      off('zoomlevelschange', this._onZoomEnd, this).
      off('zoomend', this._onZoomEnd, this).
      off('touchstart', this._onTouch, this).
      off('click', this._onTouch, this)
    },
    deleteLastVertex: function () {
      if (this._markers.length <= 1) {return}
      var lastMarker = this._markers.pop()
      var poly = this._poly
      var latlngs = poly.getLatLngs()
      var latlng = latlngs.splice(-1, 1)[0]
      this._poly.setLatLngs(latlngs)
      this._markerGroup.removeLayer(lastMarker)
      if (poly.getLatLngs().length < 2) {this._map.removeLayer(poly)}
      this._vertexChanged(latlng, false)
    },
    addVertex: function (latlng) {
      var markersLength = this._markers.length
      if (markersLength >= 2 && !this.options.allowIntersection &&
        this._poly.newLatLngIntersects(latlng)) {
        this._showErrorTooltip()
        return
      } else if (this._errorShown) {this._hideErrorTooltip()}
      this._markers.push(this._createMarker(latlng))
      this._poly.addLatLng(latlng)
      if (this._poly.getLatLngs().length === 2) {
        this._map.addLayer(this._poly)
      }
      this._vertexChanged(latlng, true)
    },
    completeShape: function () {
      if (this._markers.length <= 1 || !this._shapeIsValid()) {return}
      this._fireCreatedEvent()
      this.disable()
      if (this.options.repeatMode) {this.enable()}
    },
    _finishShape: function () {
      var latlngs = this._poly._defaultShape
        ? this._poly._defaultShape()
        : this._poly.getLatLngs()
      var intersects = this._poly.newLatLngIntersects(
        latlngs[latlngs.length - 1])
      if ((!this.options.allowIntersection && intersects) ||
        !this._shapeIsValid()) {
        this._showErrorTooltip()
        return
      }
      this._fireCreatedEvent()
      this.disable()
      if (this.options.repeatMode) {this.enable()}
    },
    _shapeIsValid: function () {return true},
    _onZoomEnd: function () {
      if (this._markers !== null) {this._updateGuide()}
    },
    _onMouseMove: function (e) {
      var newPos = this._map.mouseEventToLayerPoint(e.originalEvent)
      var latlng = this._map.layerPointToLatLng(newPos)
      this._currentLatLng = latlng
      this._updateTooltip(latlng)
      this._updateGuide(newPos)
      this._mouseMarker.setLatLng(latlng)
      DomEvent.preventDefault(e.originalEvent)
    },
    _vertexChanged: function (latlng, added) {
      this._map.fire(Draw.Event.DRAWVERTEX, {
        layers: this._markerGroup,
        sourceId: this._winfo_id,
        sourceData: this
      })
      this._updateFinishHandler()
      this._updateRunningMeasure(latlng, added)
      this._clearGuides()
      this._updateTooltip()
    },
    _onMouseDown: function (e) {
      if (!this._clickHandled && !this._touchHandled && !this._disableMarkers) {
        this._onMouseMove(e)
        this._clickHandled = true
        this._disableNewMarkers()
        var originalEvent = e.originalEvent
        var clientX = originalEvent.clientX
        var clientY = originalEvent.clientY
        this._startPoint.call(this, clientX, clientY)
      }
    },
    _startPoint: function (clientX, clientY) {
      this._mouseDownOrigin = new Point(clientX, clientY)
    },
    _onMouseUp: function (e) {
      var originalEvent = e.originalEvent
      var clientX = originalEvent.clientX
      var clientY = originalEvent.clientY
      this._endPoint.call(this, clientX, clientY, e)
      this._clickHandled = null
    },
    _endPoint: function (clientX, clientY, e) {
      if (this._mouseDownOrigin) {
        var dragCheckDistance = new Point(clientX, clientY).distanceTo(
          this._mouseDownOrigin)
        var lastPtDistance = this._calculateFinishDistance(e.latlng)
        if (this.options.maxPoints > 1 && this.options.maxPoints ==
          this._markers.length + 1) {
          this.addVertex(e.latlng)
          this._finishShape()
        } else if (lastPtDistance < 10 &&
          Browser.touch) {this._finishShape()} else if (Math.abs(
          dragCheckDistance) < 9 *
          (window.devicePixelRatio || 1)) {this.addVertex(e.latlng)}
        this._enableNewMarkers()
      }
      this._mouseDownOrigin = null
    },
    _onTouch: function (e) {
      var originalEvent = e.originalEvent
      var clientX
      var clientY
      if (originalEvent.touches && originalEvent.touches[0] &&
        !this._clickHandled && !this._touchHandled && !this._disableMarkers) {
        clientX = originalEvent.touches[0].clientX
        clientY = originalEvent.touches[0].clientY
        this._disableNewMarkers()
        this._touchHandled = true
        this._startPoint.call(this, clientX, clientY)
        this._endPoint.call(this, clientX, clientY, e)
        this._touchHandled = null
      }
      this._clickHandled = null
    },
    _onMouseOut: function () {
      if (this._tooltip) {
        this._tooltip._onMouseOut.call(this._tooltip)
      }
    },
    _calculateFinishDistance: function (potentialLatLng) {
      var lastPtDistance
      if (this._markers.length > 0) {
        var finishMarker
        if (this.type === Draw.Polyline.TYPE) {
          finishMarker = this._markers[this._markers.length - 1]
        } else if (this.type ===
          Draw.Polygon.TYPE) {finishMarker = this._markers[0]} else {return Infinity}
        var lastMarkerPoint = this._map.latLngToContainerPoint(
          finishMarker.getLatLng())
        var potentialMarker = new Marker(potentialLatLng, {
          icon: this.options.icon,
          zIndexOffset: this.options.zIndexOffset * 2
        })
        var potentialMarkerPint = this._map.latLngToContainerPoint(
          potentialMarker.getLatLng())
        lastPtDistance = lastMarkerPoint.distanceTo(potentialMarkerPint)
      } else {lastPtDistance = Infinity}
      return lastPtDistance
    },
    _updateFinishHandler: function () {
      var markerCount = this._markers.length
      if (markerCount > 1) {
        this._markers[markerCount - 1].on('click', this._finishShape, this)
      }
      if (markerCount > 2) {
        this._markers[markerCount - 2].off('click', this._finishShape, this)
      }
    },
    _createMarker: function (latlng) {
      var marker = new Marker(latlng, {
        icon: this.options.icon,
        zIndexOffset: this.options.zIndexOffset * 2
      })
      this._markerGroup.addLayer(marker)
      return marker
    },
    _updateGuide: function (newPos) {
      var markerCount = this._markers
        ? this._markers.length
        : 0
      if (markerCount > 0) {
        newPos = newPos || this._map.latLngToLayerPoint(this._currentLatLng)
        this._clearGuides()
        this._drawGuide(this._map.latLngToLayerPoint(
          this._markers[markerCount - 1].getLatLng()), newPos)
      }
    },
    _updateTooltip: function (latLng) {
      var text = this._getTooltipText()
      if (latLng) {this._tooltip.updatePosition(latLng)}
      if (!this._errorShown) {this._tooltip.updateContent(text)}
    },
    _drawGuide: function (pointA, pointB) {
      var length = Math.floor(Math.sqrt(Math.pow((pointB.x - pointA.x), 2) +
        Math.pow((pointB.y - pointA.y), 2)))
      var guidelineDistance = this.options.guidelineDistance
      var maxGuideLineLength = this.options.maxGuideLineLength
      var i = length > maxGuideLineLength
        ? length - maxGuideLineLength
        : guidelineDistance
      var fraction
      var dashPoint
      var dash
      if (!this._guidesContainer) {
        this._guidesContainer = DomUtil.create('div', 'winfo-draw-guides',
          this._overlayPane)
      }
      for (; i < length; i += this.options.guidelineDistance) {
        fraction = i / length
        dashPoint = {
          x: Math.floor((pointA.x * (1 - fraction)) + (fraction * pointB.x)),
          y: Math.floor((pointA.y * (1 - fraction)) + (fraction * pointB.y))
        }
        dash = DomUtil.create('div', 'winfo-draw-guide-dash',
          this._guidesContainer)
        dash.style.backgroundColor = !this._errorShown
          ? this.options.shapeOptions.color
          : this.options.drawError.color
        DomUtil.setPosition(dash, dashPoint)
      }
    },
    _updateGuideColor: function (color) {
      if (this._guidesContainer) {
        for (var i = 0, l = this._guidesContainer.childNodes.length; i <
        l; i++) {this._guidesContainer.childNodes[i].style.backgroundColor = color}
      }
    },
    _clearGuides: function () {
      if (this._guidesContainer) {
        while (this._guidesContainer.firstChild) {
          this._guidesContainer.removeChild(this._guidesContainer.firstChild)
        }
      }
    },
    _getTooltipText: function () {
      var showLength = this.options.showLength
      var labelText
      var distanceStr
      if (this._markers.length ===
        0) {labelText = { text: Draw.drawLocal.polyline.tooltip.start }} else {
        distanceStr = ''
        distanceStr = showLength ? this._getMeasurementString() : ''
        if (this._markers.length === 1) {
          labelText = {
            text: Draw.drawLocal.polyline.tooltip.cont,
            subtext: distanceStr
          }
        } else {
          labelText = {
            text: Draw.drawLocal.polyline.tooltip.end,
            subtext: distanceStr
          }
        }
      }
      return labelText
    },
    _updateRunningMeasure: function (latlng, added) {
      var markersLength = this._markers.length
      var previousMarkerIndex
      var distance
      if (this._markers.length ===
        1) {this._measurementRunningTotal = 0} else {
        previousMarkerIndex = markersLength - (added ? 2 : 1)
        distance = this._map.distance(latlng,
          this._markers[previousMarkerIndex].getLatLng()) *
          (this.options.factor || 1)
        this._measurementRunningTotal += distance * (added ? 1 : -1)
      }
    },
    _getMeasurementString: function () {
      var currentLatLng = this._currentLatLng
      var previousLatLng = this._markers[this._markers.length - 1].getLatLng()
      var distance
      distance = previousLatLng && currentLatLng
        ? this._measurementRunningTotal +
        this._map.distance(currentLatLng, previousLatLng) *
        (this.options.factor || 1)
        : this._measurementRunningTotal || 0
      return GeometryUtil.readableDistance(distance, this.options.metric,
        this.options.feet, this.options.nautic, this.options.precision)
    },
    _showErrorTooltip: function () {
      this._errorShown = true
      this._tooltip.showAsError().
      updateContent({ text: this.options.drawError.message })
      this._updateGuideColor(this.options.drawError.color)
      this._poly.setStyle({ color: this.options.drawError.color })
      this._clearHideErrorTimeout()
      this._hideErrorTimeout = setTimeout(
        Util.bind(this._hideErrorTooltip, this),
        this.options.drawError.timeout)
    },
    _hideErrorTooltip: function () {
      this._errorShown = false
      this._clearHideErrorTimeout()
      this._tooltip.removeError().updateContent(this._getTooltipText())
      this._updateGuideColor(this.options.shapeOptions.color)
      this._poly.setStyle({ color: this.options.shapeOptions.color })
    },
    _clearHideErrorTimeout: function () {
      if (this._hideErrorTimeout) {
        clearTimeout(this._hideErrorTimeout)
        this._hideErrorTimeout = null
      }
    },
    _disableNewMarkers: function () {this._disableMarkers = true},
    _enableNewMarkers: function () {
      setTimeout(function () {this._disableMarkers = false}.bind(this), 50)
    },
    _cleanUpShape: function () {
      if (this._markers.length > 1) {
        this._markers[this._markers.length - 1].off('click', this._finishShape,
          this)
      }
    },
    _fireCreatedEvent: function () {
      var poly = new this.Poly(this._poly.getLatLngs(),
        this.options.shapeOptions)
      Draw.Feature.prototype._fireCreatedEvent.call(this, poly)
    }
  })
  Draw.Polygon = Draw.Polyline.extend({
    statics: { TYPE: 'polygon' },
    Poly: Polygon,
    options: {
      events: {},
      showArea: true,
      showLength: false,
      shapeOptions: {
        stroke: true,
        color: '#3388ff',
        weight: 4,
        opacity: 0.5,
        fill: true,
        fillColor: null,
        fillOpacity: 0.2,
        clickable: true
      },
      metric: true,
      feet: false,
      nautic: false,
      precision: {}
    },
    initialize: function (options) {
      extendOptions(this.options, options)
      return this
    },
    addTo: function (map) {
      this._map = map
      Draw.Polyline.prototype.initialize.call(this, this.options)
      Draw.Polyline.prototype.addTo.call(this, map)
      this.type = Draw.Polygon.TYPE
      return this
    },
    _updateFinishHandler: function () {
      var markerCount = this._markers.length
      if (markerCount === 1) {
        this._markers[0].on('click', this._finishShape, this)
      }
      if (markerCount > 2) {
        this._markers[markerCount - 1].on('dblclick', this._finishShape, this)
        if (markerCount > 3) {
          this._markers[markerCount - 2].off('dblclick', this._finishShape,
            this)
        }
      }
    },
    _getTooltipText: function () {
      var text, subtext
      if (this._markers.length ===
        0) {text = Draw.drawLocal.polygon.tooltip.start} else if (this._markers.length <
        3) {
        text = Draw.drawLocal.polygon.tooltip.cont
        subtext = this._getMeasurementString()
      } else {
        text = Draw.drawLocal.polygon.tooltip.end
        subtext = this._getMeasurementString()
      }
      return {
        text: text,
        subtext: subtext
      }
    },
    _getMeasurementString: function () {
      var area = this._area
      var measurementString = ''
      if (!area && !this.options.showLength) {return null}
      if (this.options.showLength) {
        measurementString = Draw.Polyline.prototype._getMeasurementString.call(
          this)
      }
      if (area) {
        measurementString += '<br>' +
          GeometryUtil.readableArea(area, this.options.metric,
            this.options.precision)
      }
      return measurementString
    },
    _shapeIsValid: function () {return this._markers.length >= 3},
    _vertexChanged: function (latlng, added) {
      var latLngs
      if (!this.options.allowIntersection && this.options.showArea) {
        latLngs = this._poly.getLatLngs()
        this._area = GeometryUtil.geodesicArea(latLngs)
      }
      Draw.Polyline.prototype._vertexChanged.call(this, latlng, added)
    },
    _cleanUpShape: function () {
      var markerCount = this._markers.length
      if (markerCount > 0) {
        this._markers[0].off('click', this._finishShape, this)
        if (markerCount > 2) {
          this._markers[markerCount - 1].off('dblclick', this._finishShape,
            this)
        }
      }
    }
  })
  Draw.SimpleShape = Draw.Feature.extend({
    options: {
      events: {},
      repeatMode: false,
      editable: false
    },
    initialize: function (map, options) {
      this._endLabelText = Draw.drawLocal.simpleshape.tooltip.end
      Draw.Feature.prototype.initialize.call(this, map, options)
    },
    addHooks: function () {
      Draw.Feature.prototype.addHooks.call(this)
      if (this._map) {
        this._mapDraggable = this._map.dragging.enabled()
        if (this._mapDraggable) {this._map.dragging.disable()}
        this._container.style.cursor = 'crosshair'
        this._tooltip.updateContent({ text: this._initialLabelText })
        this._map.on('mousedown', this._onMouseDown, this).
        on('mousemove', this._onMouseMove, this).
        on('touchstart', this._onMouseDown, this).
        on('touchmove', this._onMouseMove, this)
        document.addEventListener('touchstart', DomEvent.preventDefault,
          { passive: false })
      }
    },
    removeHooks: function () {
      Draw.Feature.prototype.removeHooks.call(this)
      if (this._map) {
        if (this._mapDraggable) {this._map.dragging.enable()}
        this._container.style.cursor = ''
        this._map.off('mousedown', this._onMouseDown, this).
        off('mousemove', this._onMouseMove, this).
        off('touchstart', this._onMouseDown, this).
        off('touchmove', this._onMouseMove, this)
        DomEvent.off(document, 'mouseup', this._onMouseUp, this)
        DomEvent.off(document, 'touchend', this._onMouseUp, this)
        document.removeEventListener('touchstart', DomEvent.preventDefault)
        if (this._shape) {
          this._map.removeLayer(this._shape)
          delete this._shape
        }
      }
      this._isDrawing = false
    },
    _getTooltipText: function () {return { text: this._endLabelText }},
    _onMouseDown: function (e) {
      this._isDrawing = true
      this._startLatLng = e.latlng
      DomEvent.on(document, 'mouseup', this._onMouseUp, this).
      on(document, 'touchend', this._onMouseUp, this).
      preventDefault(e.originalEvent)
    },
    _onMouseMove: function (e) {
      var latlng = e.latlng
      this._tooltip.updatePosition(latlng)
      if (this._isDrawing) {
        this._tooltip.updateContent(this._getTooltipText())
        this._drawShape(latlng)
      }
    },
    _onMouseUp: function () {
      if (this._shape) {this._fireCreatedEvent()}
      this.disable()
      if (this.options.repeatMode) {this.enable()}
    }
  })
  Draw.Rectangle = Draw.SimpleShape.extend({
    statics: { TYPE: 'rectangle' },
    options: {
      shapeOptions: {
        stroke: true,
        color: '#3388ff',
        weight: 4,
        opacity: 0.5,
        fill: true,
        fillColor: null,
        fillOpacity: 0.2,
        clickable: true
      },
      showArea: true,
      metric: true
    },
    initialize: function (options) {
      this.type = Draw.Rectangle.TYPE
      this._initialLabelText = Draw.drawLocal.rectangle.tooltip.start
      extendOptions(this.options, options)
      return this
    },
    addTo: function (map) {
      this._map = map
      Draw.SimpleShape.prototype.initialize.call(this, map, this.options)
      return this
    },
    disable: function () {
      if (!this._enabled) {return}
      this._isCurrentlyTwoClickDrawing = false
      Draw.SimpleShape.prototype.disable.call(this)
    },
    _onMouseUp: function (e) {
      if (!this._shape && !this._isCurrentlyTwoClickDrawing) {
        this._isCurrentlyTwoClickDrawing = true
        return
      }
      if (this._isCurrentlyTwoClickDrawing &&
        !_hasAncestor(e.target, 'winfo-pane')) {return}
      Draw.SimpleShape.prototype._onMouseUp.call(this)
    },
    _drawShape: function (latlng) {
      if (!this._shape) {
        this._shape = new Rectangle(new LatLngBounds(this._startLatLng, latlng),
          this.options.shapeOptions)
        this._map.addLayer(this._shape)
      } else {
        this._shape.setBounds(new LatLngBounds(this._startLatLng, latlng))
      }
    },
    _fireCreatedEvent: function () {
      var rectangle = new Rectangle(this._shape.getBounds(),
        this.options.shapeOptions)
      Draw.SimpleShape.prototype._fireCreatedEvent.call(this, rectangle)
    },
    _getTooltipText: function () {
      var tooltipText = Draw.SimpleShape.prototype._getTooltipText.call(this)
      var shape = this._shape
      var showArea = this.options.showArea
      var latLngs
      var area
      var subtext
      if (shape) {
        latLngs = this._shape._defaultShape
          ? this._shape._defaultShape()
          : this._shape.getLatLngs()
        area = GeometryUtil.geodesicArea(latLngs)
        subtext = showArea ? GeometryUtil.readableArea(area,
          this.options.metric) : ''
      }
      return {
        text: tooltipText.text,
        subtext: subtext
      }
    }
  })

  function _hasAncestor (el, cls) {
    while ((el = el.parentElement) && !el.classList.contains(cls)) {}
    return el
  }

  Draw.Marker = Draw.Feature.extend({
    statics: { TYPE: 'marker' },
    options: {
      events: {},
      icon: new Icon.Default(),
      repeatMode: false,
      zIndexOffset: 2000
    },
    initialize: function (options) {
      this.type = Draw.Marker.TYPE
      this._initialLabelText = Draw.drawLocal.marker.tooltip.start
      extendOptions(this.options, options)
      return this
    },
    addTo: function (map) {
      this._map = map
      Draw.Feature.prototype.initialize.call(this, map, this.options)
      return this
    },
    addHooks: function () {
      Draw.Feature.prototype.addHooks.call(this)
      if (this._map) {
        this._tooltip.updateContent({ text: this._initialLabelText })
        if (!this._mouseMarker) {
          this._mouseMarker = new Marker(this._map.getCenter(), {
            icon: new DivIcon({
              className: 'winfo-mouse-marker',
              iconAnchor: [20, 20],
              iconSize: [40, 40]
            }),
            opacity: 0,
            zIndexOffset: this.options.zIndexOffset
          })
        }
        this._mouseMarker.on('click', this._onClick, this).addTo(this._map)
        this._map.on('mousemove', this._onMouseMove, this)
        this._map.on('click', this._onTouch, this)
      }
    },
    removeHooks: function () {
      Draw.Feature.prototype.removeHooks.call(this)
      if (this._map) {
        this._map.off('click', this._onClick, this).
        off('click', this._onTouch, this)
        if (this._marker) {
          this._marker.off('click', this._onClick, this)
          this._map.removeLayer(this._marker)
          delete this._marker
        }
        this._mouseMarker.off('click', this._onClick, this)
        this._map.removeLayer(this._mouseMarker)
        delete this._mouseMarker
        this._map.off('mousemove', this._onMouseMove, this)
      }
    },
    _onMouseMove: function (e) {
      var latlng = e.latlng
      this._tooltip.updatePosition(latlng)
      this._mouseMarker.setLatLng(latlng)
      if (!this._marker) {
        this._marker = this._createMarker(latlng)
        this._marker.on('click', this._onClick, this)
        this._map.on('click', this._onClick, this).addLayer(this._marker)
      } else {
        latlng = this._mouseMarker.getLatLng()
        this._marker.setLatLng(latlng)
      }
    },
    _createMarker: function (latlng) {
      return new Marker(latlng, {
        icon: this.options.icon,
        zIndexOffset: this.options.zIndexOffset
      })
    },
    _onClick: function () {
      this._fireCreatedEvent()
      this.disable()
      if (this.options.repeatMode) {this.enable()}
    },
    _onTouch: function (e) {
      this._onMouseMove(e)
      this._onClick()
    },
    _fireCreatedEvent: function () {
      var marker = new Marker.Touch(this._marker.getLatLng(),
        { icon: this.options.icon })
      Draw.Feature.prototype._fireCreatedEvent.call(this, marker)
    }
  })
  Draw.marker = function (options) {return new Draw.Marker(options)}
  Draw.CircleMarker = Draw.Marker.extend({
    statics: { TYPE: 'circlemarker' },
    options: {
      stroke: true,
      color: '#3388ff',
      weight: 4,
      opacity: 0.5,
      fill: true,
      fillColor: null,
      fillOpacity: 0.2,
      clickable: true,
      zIndexOffset: 2000
    },
    initialize: function (map, options) {
      this.type = Draw.CircleMarker.TYPE
      this._initialLabelText = Draw.drawLocal.circlemarker.tooltip.start
      Draw.Feature.prototype.initialize.call(this, map, options)
    },
    _fireCreatedEvent: function () {
      var circleMarker = new CircleMarker(this._marker.getLatLng(),
        this.options)
      Draw.Feature.prototype._fireCreatedEvent.call(this, circleMarker)
    },
    _createMarker: function (latlng) {
      return new CircleMarker(latlng, this.options)
    }
  })
  Draw.Circle = Draw.SimpleShape.extend({
    statics: { TYPE: 'circle' },
    options: {
      shapeOptions: {
        stroke: true,
        color: '#3388ff',
        weight: 4,
        opacity: 0.5,
        fill: true,
        fillColor: null,
        fillOpacity: 0.2,
        clickable: true
      },
      showRadius: true,
      metric: true,
      feet: true,
      nautic: false
    },
    initialize: function (options) {
      this.type = Draw.Circle.TYPE
      this._initialLabelText = Draw.drawLocal.circle.tooltip.start
      extendOptions(this.options, options)
      return this
    },
    addTo: function (map) {
      this._map = map
      Draw.SimpleShape.prototype.initialize.call(this, map, this.options)
      return this
    },
    _drawShape: function (latlng) {
      var distance = this._map.distance(this._startLatLng, latlng)
      if (!this._shape) {
        this._shape = new Circle(this._startLatLng, distance,
          this.options.shapeOptions)
        this._map.addLayer(this._shape)
      } else {this._shape.setRadius(distance)}
    },
    _fireCreatedEvent: function () {
      var circle = new Circle(this._startLatLng, this._shape.getRadius(),
        this.options.shapeOptions)
      Draw.SimpleShape.prototype._fireCreatedEvent.call(this, circle)
    },
    _onMouseMove: function (e) {
      var latlng = e.latlng
      var showRadius = this.options.showRadius
      var useMetric = this.options.metric
      var radius
      this._tooltip.updatePosition(latlng)
      if (this._isDrawing) {
        this._drawShape(latlng)
        radius = this._shape.getRadius().toFixed(1)
        var subtext = ''
        if (showRadius) {
          this._isDrawing && this.options.events &&
          this.options.events.drawingMouseMove &&
          this.options.events.drawingMouseMove({
            circle: this,
            newLatLng: latlng
          })
          subtext = Draw.drawLocal.circle.radius + ': ' +
            GeometryUtil.readableDistance(radius, useMetric,
              this.options.feet, this.options.nautic)
        }
        this._tooltip.updateContent({
          text: this._endLabelText,
          subtext: subtext
        })
        this.mousemove
      }
    }
  })
  var Edit = {
    Marker: null,
    Poly: null,
    PolyVerticesEdit: null,
    SimpleShape: null,
    Rectangle: null,
    CircleMarker: null,
    Circle: null
  }
  Edit.Marker = Handler.extend({
    initialize: function (marker, options) {
      this._marker = marker
      setOptions(this, options)
    },
    addHooks: function () {
      var marker = this._marker
      marker.dragging.enable()
      marker.on('dragend', this._onDragEnd, marker)
      this._toggleMarkerHighlight()
    },
    removeHooks: function () {
      var marker = this._marker
      marker.dragging.disable()
      marker.off('dragend', this._onDragEnd, marker)
      this._toggleMarkerHighlight()
    },
    _onDragEnd: function (e) {
      var layer = e.target
      layer.edited = true
      this._map.fire(Draw.Event.EDITMOVE, { layer: layer })
    },
    _toggleMarkerHighlight: function () {
      var icon = this._marker._icon
      if (!icon) {return}
      icon.style.display = 'none'
      if (DomUtil.hasClass(icon, 'winfo-edit-marker-selected')) {
        DomUtil.removeClass(icon, 'winfo-edit-marker-selected')
        this._offsetMarker(icon, -4)
      } else {
        DomUtil.addClass(icon, 'winfo-edit-marker-selected')
        this._offsetMarker(icon, 4)
      }
      icon.style.display = ''
    },
    _offsetMarker: function (icon, offset) {
      var iconMarginTop = parseInt(icon.style.marginTop, 10) - offset
      var iconMarginLeft = parseInt(icon.style.marginLeft, 10) - offset
      icon.style.marginTop = iconMarginTop + 'px'
      icon.style.marginLeft = iconMarginLeft + 'px'
    }
  })
  Marker.addInitHook(function () {
    if (Edit.Marker) {
      this.editing = new Edit.Marker(this)
      if (this.options.editable) {this.editing.enable()}
    }
  })
  Edit.Poly = Handler.extend({
    initialize: function (poly) {
      this.latlngs = [poly._latlngs]
      if (poly._holes) {this.latlngs = this.latlngs.concat(poly._holes)}
      this._poly = poly
      this._poly.on('revert-edited', this._updateLatLngs, this)
    },
    _defaultShape: function () {
      if (!Polyline._flat) {return this._poly._latlngs}
      return Polyline._flat(this._poly._latlngs)
        ? this._poly._latlngs
        : this._poly._latlngs[0]
    },
    _eachVertexHandler: function (callback) {
      for (var i = 0; i < this._verticesHandlers.length; i++) {
        callback(this._verticesHandlers[i])
      }
    },
    addHooks: function () {
      this._initHandlers()
      this._eachVertexHandler(function (handler) {handler.addHooks()})
    },
    removeHooks: function () {
      this._eachVertexHandler(function (handler) {handler.removeHooks()})
    },
    updateMarkers: function () {
      this._eachVertexHandler(function (handler) {handler.updateMarkers()})
    },
    _initHandlers: function () {
      this._verticesHandlers = []
      for (var i = 0; i < this.latlngs.length; i++) {
        this._verticesHandlers.push(
          new Edit.PolyVerticesEdit(this._poly, this.latlngs[i],
            this._poly.options.poly))
      }
    },
    _updateLatLngs: function (e) {
      this.latlngs = [e.layer._latlngs]
      if (e.layer._holes) {this.latlngs = this.latlngs.concat(e.layer._holes)}
    }
  })
  Edit.PolyVerticesEdit = Handler.extend({
    options: {
      icon: new DivIcon({
        iconSize: new Point(8, 8),
        className: 'winfo-div-icon winfo-editing-icon'
      }),
      touchIcon: new DivIcon({
        iconSize: new Point(20, 20),
        className: 'winfo-div-icon winfo-editing-icon winfo-touch-icon'
      }),
      drawError: {
        color: '#b00b00',
        timeout: 1000
      }
    },
    initialize: function (poly, latlngs, options) {
      if (Browser.touch) {this.options.icon = this.options.touchIcon}
      this._poly = poly
      if (options && options.drawError) {
        options.drawError = Util.extend({}, this.options.drawError,
          options.drawError)
      }
      this._latlngs = latlngs
      setOptions(this, options)
    },
    _defaultShape: function () {
      if (!Polyline._flat) {return this._latlngs}
      return Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0]
    },
    addHooks: function () {
      var poly = this._poly
      var path = poly._path
      if (!(poly instanceof Polygon)) {
        poly.options.fill = false
        if (poly.options.editing) {poly.options.editing.fill = false}
      }
      if (path) {
        if (poly.options.editing && poly.options.editing.className) {
          if (poly.options.original.className) {
            poly.options.original.className.split(' ').
            forEach(
              function (className) {DomUtil.removeClass(path, className)})
          }
          poly.options.editing.className.split(' ').
          forEach(function (className) {DomUtil.addClass(path, className)})
        }
      }
      poly.setStyle(poly.options.editing)
      if (this._poly._map) {
        this._map = this._poly._map
        if (!this._markerGroup) {this._initMarkers()}
        this._poly._map.addLayer(this._markerGroup)
      }
    },
    removeHooks: function () {
      var poly = this._poly
      var path = poly._path
      if (path) {
        if (poly.options.editing && poly.options.editing.className) {
          poly.options.editing.className.split(' ').
          forEach(function (className) {DomUtil.removeClass(path, className)})
          if (poly.options.original.className) {
            poly.options.original.className.split(' ').
            forEach(function (className) {DomUtil.addClass(path, className)})
          }
        }
      }
      poly.setStyle(poly.options.original)
      if (poly._map) {
        poly._map.removeLayer(this._markerGroup)
        delete this._markerGroup
        delete this._markers
      }
    },
    updateMarkers: function () {
      this._markerGroup.clearLayers()
      this._initMarkers()
    },
    _initMarkers: function () {
      if (!this._markerGroup) {this._markerGroup = new LayerGroup()}
      this._markers = []
      var latlngs = this._defaultShape()
      var i
      var j
      var len
      var marker
      for (i = 0, len = latlngs.length; i < len; i++) {
        marker = this._createMarker(latlngs[i], i)
        marker.on('click', this._onMarkerClick, this)
        marker.on('contextmenu', this._onContextMenu, this)
        this._markers.push(marker)
      }
      var markerLeft, markerRight
      for (i = 0, j = len - 1; i < len; j = i++) {
        if (i === 0 &&
          !(Polygon && (this._poly instanceof Polygon))) {continue}
        markerLeft = this._markers[j]
        markerRight = this._markers[i]
        this._createMiddleMarker(markerLeft, markerRight)
        this._updatePrevNext(markerLeft, markerRight)
      }
    },
    _createMarker: function (latlng, index) {
      var marker = new Marker.Touch(latlng, {
        draggable: true,
        icon: this.options.icon
      })
      marker._origLatLng = latlng
      marker._index = index
      marker.on('dragstart', this._onMarkerDragStart, this).
      on('drag', this._onMarkerDrag, this).
      on('dragend', this._fireEdit, this).
      on('touchmove', this._onTouchMove, this).
      on('touchend', this._fireEdit, this).
      on('MSPointerMove', this._onTouchMove, this).
      on('MSPointerUp', this._fireEdit, this)
      this._markerGroup.addLayer(marker)
      return marker
    },
    _onMarkerDragStart: function () {this._poly.fire('editstart')},
    _spliceLatLngs: function () {
      var latlngs = this._defaultShape()
      var removed = [].splice.apply(latlngs, arguments)
      this._poly._convertLatLngs(latlngs, true)
      this._poly.redraw()
      return removed
    },
    _removeMarker: function (marker) {
      var i = marker._index
      this._markerGroup.removeLayer(marker)
      this._markers.splice(i, 1)
      this._spliceLatLngs(i, 1)
      this._updateIndexes(i, -1)
      marker.off('dragstart', this._onMarkerDragStart, this).
      off('drag', this._onMarkerDrag, this).
      off('dragend', this._fireEdit, this).
      off('touchmove', this._onMarkerDrag, this).
      off('touchend', this._fireEdit, this).
      off('click', this._onMarkerClick, this).
      off('MSPointerMove', this._onTouchMove, this).
      off('MSPointerUp', this._fireEdit, this)
    },
    _fireEdit: function () {
      this._poly.edited = true
      this._poly.fire('edit')
      this._poly._map.fire(Draw.Event.EDITVERTEX, {
        layers: this._markerGroup,
        poly: this._poly
      })
    },
    _onMarkerDrag: function (e) {
      var marker = e.target
      var poly = this._poly
      var oldOrigLatLng = LatLngUtil.cloneLatLng(marker._origLatLng)
      extend(marker._origLatLng, marker._latlng)
      if (poly.options.poly) {
        var tooltip = poly._map._editTooltip
        if (!poly.options.poly.allowIntersection && poly.intersects()) {
          extend(marker._origLatLng, oldOrigLatLng)
          marker.setLatLng(oldOrigLatLng)
          var originalColor = poly.options.color
          poly.setStyle({ color: this.options.drawError.color })
          if (tooltip) {
            tooltip.updateContent({ text: Draw.drawLocal.polyline.error })
          }
          setTimeout(function () {
            poly.setStyle({ color: originalColor })
            if (tooltip) {
              tooltip.updateContent({
                text: Draw.drawLocal.edit.handlers.edit.tooltip.text,
                subtext: Draw.drawLocal.edit.handlers.edit.tooltip.subtext
              })
            }
          }, 1000)
        }
      }
      if (marker._middleLeft) {
        marker._middleLeft.setLatLng(
          this._getMiddleLatLng(marker._prev, marker))
      }
      if (marker._middleRight) {
        marker._middleRight.setLatLng(
          this._getMiddleLatLng(marker, marker._next))
      }
      this._poly._bounds._southWest = new LatLng(Infinity, Infinity)
      this._poly._bounds._northEast = new LatLng(-Infinity, -Infinity)
      var latlngs = this._poly.getLatLngs()
      this._poly._convertLatLngs(latlngs, true)
      this._poly.redraw()
      this._poly.fire('editdrag')
    },
    _onMarkerClick: function (e) {
      var minPoints = new Polygon && (this._poly instanceof new Polygon)
        ? 4
        : 3
      var marker = e.target
      if (this._defaultShape().length < minPoints) {return}
      this._removeMarker(marker)
      this._updatePrevNext(marker._prev, marker._next)
      if (marker._middleLeft) {
        this._markerGroup.removeLayer(marker._middleLeft)
      }
      if (marker._middleRight) {
        this._markerGroup.removeLayer(marker._middleRight)
      }
      if (marker._prev && marker._next) {
        this._createMiddleMarker(marker._prev, marker._next)
      } else if (!marker._prev) {marker._next._middleLeft = null} else if (!marker._next) {marker._prev._middleRight = null}
      this._fireEdit()
    },
    _onContextMenu: function (e) {
      var marker = e.target
      var poly = this._poly
      this._poly._map.fire(Draw.Event.MARKERCONTEXT, {
        marker: marker,
        layers: this._markerGroup,
        poly: this._poly
      })
      DomEvent.stopPropagation
    },
    _onTouchMove: function (e) {
      var layerPoint = this._map.mouseEventToLayerPoint(
        e.originalEvent.touches[0])
      var latlng = this._map.layerPointToLatLng(layerPoint)
      var marker = e.target
      extend(marker._origLatLng, latlng)
      if (marker._middleLeft) {
        marker._middleLeft.setLatLng(
          this._getMiddleLatLng(marker._prev, marker))
      }
      if (marker._middleRight) {
        marker._middleRight.setLatLng(
          this._getMiddleLatLng(marker, marker._next))
      }
      this._poly.redraw()
      this.updateMarkers()
    },
    _updateIndexes: function (index, delta) {
      this._markerGroup.eachLayer(function (marker) {
        if (marker._index > index) {marker._index += delta}
      })
    },
    _createMiddleMarker: function (marker1, marker2) {
      var latlng = this._getMiddleLatLng(marker1, marker2)
      var marker = this._createMarker(latlng)
      var onClick
      var onDragStart
      var onDragEnd
      marker.setOpacity(0.6)
      marker1._middleRight = marker2._middleLeft = marker
      onDragStart = function () {
        marker.off('touchmove', onDragStart, this)
        var i = marker2._index
        marker._index = i
        marker.off('click', onClick, this).
        on('click', this._onMarkerClick, this)
        latlng.lat = marker.getLatLng().lat
        latlng.lng = marker.getLatLng().lng
        this._spliceLatLngs(i, 0, latlng)
        this._markers.splice(i, 0, marker)
        marker.setOpacity(1)
        this._updateIndexes(i, 1)
        marker2._index++
        this._updatePrevNext(marker1, marker)
        this._updatePrevNext(marker, marker2)
        this._poly.fire('editstart')
      }
      onDragEnd = function () {
        marker.off('dragstart', onDragStart, this)
        marker.off('dragend', onDragEnd, this)
        marker.off('touchmove', onDragStart, this)
        this._createMiddleMarker(marker1, marker)
        this._createMiddleMarker(marker, marker2)
      }
      onClick = function () {
        onDragStart.call(this)
        onDragEnd.call(this)
        this._fireEdit()
      }
      marker.on('click', onClick, this).
      on('dragstart', onDragStart, this).
      on('dragend', onDragEnd, this).
      on('touchmove', onDragStart, this)
      this._markerGroup.addLayer(marker)
    },
    _updatePrevNext: function (marker1, marker2) {
      if (marker1) {marker1._next = marker2}
      if (marker2) {marker2._prev = marker1}
    },
    _getMiddleLatLng: function (marker1, marker2) {
      var map = this._poly._map
      var p1 = map.project(marker1.getLatLng())
      var p2 = map.project(marker2.getLatLng())
      return map.unproject(p1._add(p2)._divideBy(2))
    }
  })
  Polyline.addInitHook(function () {
    if (this.editing) {return}
    if (Edit.Poly) {
      this.editing = new Edit.Poly(this)
      if (this.options.editable) {this.editing.enable()}
    }
    this.on('add', function () {
      if (this.editing && this.editing.enabled()) {this.editing.addHooks()}
    })
    this.on('remove', function () {
      if (this.editing && this.editing.enabled()) {this.editing.removeHooks()}
    })
  })
  Edit.SimpleShape = Handler.extend({
    options: {
      moveIcon: new DivIcon({
        iconSize: new Point(8, 8),
        className: 'winfo-div-icon winfo-editing-icon winfo-edit-move'
      }),
      resizeIcon: new DivIcon({
        iconSize: new Point(8, 8),
        className: 'winfo-div-icon winfo-editing-icon winfo-edit-resize'
      }),
      touchMoveIcon: new DivIcon({
        iconSize: new Point(20, 20),
        className: 'winfo-div-icon winfo-editing-icon winfo-edit-move winfo-touch-icon'
      }),
      touchResizeIcon: new DivIcon({
        iconSize: new Point(20, 20),
        className: 'winfo-div-icon winfo-editing-icon winfo-edit-resize winfo-touch-icon'
      })
    },
    initialize: function (shape, options) {
      if (Browser.touch) {
        this.options.moveIcon = this.options.touchMoveIcon
        this.options.resizeIcon = this.options.touchResizeIcon
      }
      this._shape = shape
      Util.setOptions(this, options)
    },
    addHooks: function () {
      var shape = this._shape
      if (this._shape._map) {
        this._map = this._shape._map
        shape.setStyle(shape.options.editing)
        if (shape._map) {
          this._map = shape._map
          if (!this._markerGroup) {this._initMarkers()}
          this._map.addLayer(this._markerGroup)
        }
      }
    },
    removeHooks: function () {
      var shape = this._shape
      shape.setStyle(shape.options.original)
      if (shape._map) {
        this._unbindMarker(this._moveMarker)
        for (var i = 0, l = this._resizeMarkers.length; i <
        l; i++) {this._unbindMarker(this._resizeMarkers[i])}
        this._resizeMarkers = null
        this._map.removeLayer(this._markerGroup)
        delete this._markerGroup
      }
      this._map = null
    },
    updateMarkers: function () {
      this._markerGroup.clearLayers()
      this._initMarkers()
    },
    _initMarkers: function () {
      if (!this._markerGroup) {this._markerGroup = new LayerGroup()}
      this._createMoveMarker()
      this._createResizeMarker()
    },
    _createMoveMarker: function () {},
    _createResizeMarker: function () {},
    _createMarker: function (latlng, icon) {
      var marker = new Marker.Touch(latlng, {
        draggable: true,
        icon: icon,
        zIndexOffset: 10
      })
      this._bindMarker(marker)
      this._markerGroup.addLayer(marker)
      return marker
    },
    _bindMarker: function (marker) {
      marker.on('dragstart', this._onMarkerDragStart, this).
      on('drag', this._onMarkerDrag, this).
      on('dragend', this._onMarkerDragEnd, this).
      on('touchstart', this._onTouchStart, this).
      on('touchmove', this._onTouchMove, this).
      on('MSPointerMove', this._onTouchMove, this).
      on('touchend', this._onTouchEnd, this).
      on('MSPointerUp', this._onTouchEnd, this)
    },
    _unbindMarker: function (marker) {
      marker.off('dragstart', this._onMarkerDragStart, this).
      off('drag', this._onMarkerDrag, this).
      off('dragend', this._onMarkerDragEnd, this).
      off('touchstart', this._onTouchStart, this).
      off('touchmove', this._onTouchMove, this).
      off('MSPointerMove', this._onTouchMove, this).
      off('touchend', this._onTouchEnd, this).
      off('MSPointerUp', this._onTouchEnd, this)
    },
    _onMarkerDragStart: function (e) {
      var marker = e.target
      marker.setOpacity(0)
      this._shape.fire('editstart')
    },
    _fireEdit: function () {
      this._shape.edited = true
      this._shape.fire('edit')
    },
    _onMarkerDrag: function (e) {
      var marker = e.target
      var latlng = marker.getLatLng()
      if (marker === this._moveMarker) {this._move(latlng)} else {
        this._resize(latlng)
      }
      this._shape.redraw()
      this._shape.fire('editdrag')
    },
    _onMarkerDragEnd: function (e) {
      var marker = e.target
      marker.setOpacity(1)
      this._fireEdit()
    },
    _onTouchStart: function (e) {
      Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e)
      if (typeof (this._getCorners) === 'function') {
        var corners = this._getCorners()
        var marker = e.target
        var currentCornerIndex = marker._cornerIndex
        marker.setOpacity(0)
        this._oppositeCorner = corners[(currentCornerIndex + 2) % 4]
        this._toggleCornerMarkers(0, currentCornerIndex)
      }
      this._shape.fire('editstart')
    },
    _onTouchMove: function (e) {
      var layerPoint = this._map.mouseEventToLayerPoint(
        e.originalEvent.touches[0])
      var latlng = this._map.layerPointToLatLng(layerPoint)
      var marker = e.target
      if (marker === this._moveMarker) {this._move(latlng)} else {
        this._resize(latlng)
      }
      this._shape.redraw()
      return false
    },
    _onTouchEnd: function (e) {
      var marker = e.target
      marker.setOpacity(1)
      this.updateMarkers()
      this._fireEdit()
    },
    _move: function () {},
    _resize: function () {}
  })
  Edit.Rectangle = Edit.SimpleShape.extend({
    _createMoveMarker: function () {
      var bounds = this._shape.getBounds()
      var center = bounds.getCenter()
      this._moveMarker = this._createMarker(center, this.options.moveIcon)
    },
    _createResizeMarker: function () {
      var corners = this._getCorners()
      this._resizeMarkers = []
      for (var i = 0, l = corners.length; i < l; i++) {
        this._resizeMarkers.push(
          this._createMarker(corners[i], this.options.resizeIcon))
        this._resizeMarkers[i]._cornerIndex = i
      }
    },
    _onMarkerDragStart: function (e) {
      Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e)
      var corners = this._getCorners()
      var marker = e.target
      var currentCornerIndex = marker._cornerIndex
      this._oppositeCorner = corners[(currentCornerIndex + 2) % 4]
      this._toggleCornerMarkers(0, currentCornerIndex)
    },
    _onMarkerDragEnd: function (e) {
      var marker = e.target
      var bounds
      var center
      if (marker === this._moveMarker) {
        bounds = this._shape.getBounds()
        center = bounds.getCenter()
        marker.setLatLng(center)
      }
      this._toggleCornerMarkers(1)
      this._repositionCornerMarkers()
      Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, e)
    },
    _move: function (newCenter) {
      var latlngs = this._shape._defaultShape
        ? this._shape._defaultShape()
        : this._shape.getLatLngs()
      var bounds = this._shape.getBounds()
      var center = bounds.getCenter()
      var offset
      var newLatLngs = []
      for (var i = 0, l = latlngs.length; i < l; i++) {
        offset = [
          latlngs[i].lat - center.lat,
          latlngs[i].lng - center.lng
        ]
        newLatLngs.push([newCenter.lat + offset[0], newCenter.lng + offset[1]])
      }
      this._shape.setLatLngs(newLatLngs)
      this._repositionCornerMarkers()
      this._map.fire(Draw.Event.EDITMOVE, { layer: this._shape })
    },
    _resize: function (latlng) {
      var bounds
      this._shape.setBounds(new LatLngBounds(latlng, this._oppositeCorner))
      bounds = this._shape.getBounds()
      this._moveMarker.setLatLng(bounds.getCenter())
      this._map.fire(Draw.Event.EDITRESIZE, { layer: this._shape })
    },
    _getCorners: function () {
      var bounds = this._shape.getBounds()
      var nw = bounds.getNorthWest()
      var ne = bounds.getNorthEast()
      var se = bounds.getSouthEast()
      var sw = bounds.getSouthWest()
      return [nw, ne, se, sw]
    },
    _toggleCornerMarkers: function (opacity) {
      for (var i = 0, l = this._resizeMarkers.length; i <
      l; i++) {this._resizeMarkers[i].setOpacity(opacity)}
    },
    _repositionCornerMarkers: function () {
      var corners = this._getCorners()
      for (var i = 0, l = this._resizeMarkers.length; i <
      l; i++) {this._resizeMarkers[i].setLatLng(corners[i])}
    }
  })
  Rectangle.addInitHook(function () {
    if (Edit.Rectangle) {
      this.editing = new Edit.Rectangle(this)
      if (this.options.editable) {this.editing.enable()}
    }
  })
  Edit.CircleMarker = Edit.SimpleShape.extend({
    _createMoveMarker: function () {
      var center = this._shape.getLatLng()
      this._moveMarker = this._createMarker(center, this.options.moveIcon)
    },
    _createResizeMarker: function () {this._resizeMarkers = []},
    _move: function (latlng) {
      if (this._resizeMarkers.length) {
        var resizemarkerPoint = this._getResizeMarkerPoint(latlng)
        this._resizeMarkers[0].setLatLng(resizemarkerPoint)
      }
      this._shape.setLatLng(latlng)
      this._map.fire(Draw.Event.EDITMOVE, { layer: this._shape })
    }
  })
  CircleMarker.addInitHook(function () {
    if (Edit.CircleMarker) {
      this.editing = new Edit.CircleMarker(this)
      if (this.options.editable) {this.editing.enable()}
    }
    this.on('add', function () {
      if (this.editing && this.editing.enabled()) {this.editing.addHooks()}
    })
    this.on('remove', function () {
      if (this.editing && this.editing.enabled()) {this.editing.removeHooks()}
    })
  })
  Edit.Circle = Edit.CircleMarker.extend({
    _createResizeMarker: function () {
      var center = this._shape.getLatLng()
      var resizemarkerPoint = this._getResizeMarkerPoint(center)
      this._resizeMarkers = []
      this._resizeMarkers.push(
        this._createMarker(resizemarkerPoint, this.options.resizeIcon))
    },
    _getResizeMarkerPoint: function (latlng) {
      var delta = this._shape._radius * Math.cos(Math.PI / 4)
      var point = this._map.project(latlng)
      return this._map.unproject([point.x + delta, point.y - delta])
    },
    _resize: function (latlng) {
      var moveLatLng = this._moveMarker.getLatLng()
      this.radius = 0
      this.radius = this._map.distance(moveLatLng, latlng)
      this._shape.setRadius(this.radius)
      if (this._map.editTooltip) {
        this._map._editTooltip.updateContent({
          text: Draw.drawLocal.edit.handlers.edit.tooltip.subtext + '<br />' +
            Draw.drawLocal.edit.handlers.edit.tooltip.text,
          subtext: Draw.drawLocal.circle.radius + ': ' +
            GeometryUtil.readableDistance(this.radius, true,
              this.options.feet, this.options.nautic)
        })
      }
      this._shape.setRadius(this.radius)
      this._map.fire(Draw.Event.EDITRESIZE, { layer: this._shape })
    }
  })
  Circle.addInitHook(function () {
    if (Edit.Circle) {
      this.editing = new Edit.Circle(this)
      if (this.options.editable) {this.editing.enable()}
    }
  })
  Map.mergeOptions({ touchExtend: true })
  Map.TouchExtend = Handler.extend({
    initialize: function (map) {
      this._map = map
      this._container = map._container
      this._pane = map._panes.overlayPane
    },
    addHooks: function () {
      DomEvent.on(this._container, 'touchstart', this._onTouchStart, this)
      DomEvent.on(this._container, 'touchend', this._onTouchEnd, this)
      DomEvent.on(this._container, 'touchmove', this._onTouchMove, this)
      if (this._detectIE()) {
        DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this)
        DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this)
        DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this)
        DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel,
          this)
      } else {
        DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this)
        DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this)
      }
    },
    removeHooks: function () {
      DomEvent.off(this._container, 'touchstart', this._onTouchStart, this)
      DomEvent.off(this._container, 'touchend', this._onTouchEnd, this)
      DomEvent.off(this._container, 'touchmove', this._onTouchMove, this)
      if (this._detectIE()) {
        DomEvent.off(this._container, 'MSPointerDown', this._onTouchStart,
          this)
        DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd, this)
        DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove, this)
        DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel,
          this)
      } else {
        DomEvent.off(this._container, 'touchcancel', this._onTouchCancel, this)
        DomEvent.off(this._container, 'touchleave', this._onTouchLeave, this)
      }
    },
    _touchEvent: function (e, type) {
      var touchEvent = {}
      if (typeof e.touches !== 'undefined') {
        if (!e.touches.length) {return}
        touchEvent = e.touches[0]
      } else if (e.pointerType === 'touch') {
        touchEvent = e
        if (!this._filterClick(e)) {return}
      } else {return}
      var containerPoint = this._map.mouseEventToContainerPoint(touchEvent)
      var layerPoint = this._map.mouseEventToLayerPoint(touchEvent)
      var latlng = this._map.layerPointToLatLng(layerPoint)
      this._map.fire(type, {
        latlng: latlng,
        layerPoint: layerPoint,
        containerPoint: containerPoint,
        pageX: touchEvent.pageX,
        pageY: touchEvent.pageY,
        originalEvent: e
      })
    },
    _filterClick: function (e) {
      var timeStamp = (e.timeStamp || e.originalEvent.timeStamp)
      var elapsed = DomEvent._lastClick && (timeStamp - DomEvent._lastClick)
      if ((elapsed && elapsed > 100 && elapsed < 500) ||
        (e.target._simulatedClick && !e._simulated)) {
        DomEvent.stop(e)
        return false
      }
      DomEvent._lastClick = timeStamp
      return true
    },
    _onTouchStart: function (e) {
      if (!this._map._loaded) {return}
      var type = 'touchstart'
      this._touchEvent(e, type)
    },
    _onTouchEnd: function (e) {
      if (!this._map._loaded) {return}
      var type = 'touchend'
      this._touchEvent(e, type)
    },
    _onTouchCancel: function (e) {
      if (!this._map._loaded) {return}
      var type = 'touchcancel'
      if (this._detectIE()) {type = 'pointercancel'}
      this._touchEvent(e, type)
    },
    _onTouchLeave: function (e) {
      if (!this._map._loaded) {return}
      var type = 'touchleave'
      this._touchEvent(e, type)
    },
    _onTouchMove: function (e) {
      if (!this._map._loaded) {return}
      var type = 'touchmove'
      this._touchEvent(e, type)
    },
    _detectIE: function () {
      var ua = window.navigator.userAgent
      var msie = ua.indexOf('MSIE ')
      if (msie > 0) {
        return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10)
      }
      var trident = ua.indexOf('Trident/')
      if (trident > 0) {
        var rv = ua.indexOf('rv:')
        return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10)
      }
      var edge = ua.indexOf('Edge/')
      if (edge > 0) {
        return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10)
      }
      return false
    }
  })
  Map.addInitHook('addHandler', 'touchExtend', Map.TouchExtend)
  Marker.Touch = Marker.extend({
    _initInteraction: function () {
      if (!this.addInteractiveTarget) {return this._initInteractionLegacy()}
      return Marker.prototype._initInteraction.apply(this)
    },
    _initInteractionLegacy: function () {
      if (!this.options.clickable) {return}
      var icon = this._icon
      var events = [
        'dblclick',
        'mousedown',
        'mouseover',
        'mouseout',
        'contextmenu',
        'touchstart',
        'touchend',
        'touchmove'
      ]
      if (this._detectIE) {
        events.concat([
          'MSPointerDown',
          'MSPointerUp',
          'MSPointerMove',
          'MSPointerCancel'
        ])
      } else {events.concat(['touchcancel'])}
      DomUtil.addClass(icon, 'winfo-clickable')
      DomEvent.on(icon, 'click', this._onMouseClick, this)
      DomEvent.on(icon, 'keypress', this._onKeyPress, this)
      for (var i = 0; i < events.length; i++) {
        DomEvent.on(icon, events[i], this._fireMouseEvent, this)
      }
      if (Handler.MarkerDrag) {
        this.dragging = new Handler.MarkerDrag(this)
        if (this.options.draggable) {this.dragging.enable()}
      }
    },
    _detectIE: function () {
      var ua = window.navigator.userAgent
      var msie = ua.indexOf('MSIE ')
      if (msie > 0) {
        return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10)
      }
      var trident = ua.indexOf('Trident/')
      if (trident > 0) {
        var rv = ua.indexOf('rv:')
        return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10)
      }
      var edge = ua.indexOf('Edge/')
      if (edge > 0) {
        return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10)
      }
      return false
    }
  })
  var LatLngUtil = {
    cloneLatLngs: function (latlngs) {
      var clone = []
      for (var i = 0, l = latlngs.length; i < l; i++) {
        if (Array.isArray(latlngs[i])) {
          clone.push(LatLngUtil.cloneLatLngs(latlngs[i]))
        } else {clone.push(this.cloneLatLng(latlngs[i]))}
      }
      return clone
    },
    cloneLatLng: function (latlng) {return new LatLng(latlng.lat, latlng.lng)}
  }
  var defaultPrecision = {
    km: 2,
    ha: 2,
    m: 0,
    mi: 2,
    ac: 2,
    yd: 0,
    ft: 0,
    nm: 2
  }
  var GeometryUtil = extend(GeometryUtil || {}, {
    geodesicArea: function (latLngs) {
      var pointsCount = latLngs.length
      var area = 0.0
      var d2r = Math.PI / 180
      var p1
      var p2
      if (pointsCount > 2) {
        for (var i = 0; i < pointsCount; i++) {
          p1 = latLngs[i]
          p2 = latLngs[(i + 1) % pointsCount]
          area += ((p2.lng - p1.lng) * d2r) *
            (2 + Math.sin(p1.lat * d2r) + Math.sin(p2.lat * d2r))
        }
        area = area * 6378137.0 * 6378137.0 / 2.0
      }
      return Math.abs(area)
    },
    formattedNumber: function (n, precision) {
      var formatted = parseFloat(n).
      toFixed(precision)
      var format = Draw.drawLocal.format && Draw.drawLocal.format.numeric
      var delimiters = format && format.delimiters
      var thousands = delimiters && delimiters.thousands
      var decimal = delimiters && delimiters.decimal
      if (thousands || decimal) {
        var splitValue = formatted.split('.')
        formatted = thousands ? splitValue[0].replace(/(\d)(?=(\d{3})+(?!\d))/g,
          '$1' + thousands) : splitValue[0]
        decimal = decimal || '.'
        if (splitValue.length > 1) {
          formatted = formatted + decimal + splitValue[1]
        }
      }
      return formatted
    },
    readableArea: function (area, isMetric, precision) {
      var areaStr
      var units
      var precision = Util.extend({}, defaultPrecision, precision)
      if (isMetric) {
        units = ['ha', 'm']
        var type = typeof isMetric
        if (type === 'string') {units = [isMetric]} else if (type !==
          'boolean') {units = isMetric}
        if (area >= 1000000 && units.indexOf('km') !== -1) {
          areaStr = GeometryUtil.formattedNumber(area * 0.000001,
            precision['km']) + ' km²'
        } else if (area >= 10000 && units.indexOf('ha') !== -1) {
          areaStr = GeometryUtil.formattedNumber(area * 0.0001,
            precision['ha']) + ' ha'
        } else {
          areaStr = GeometryUtil.formattedNumber(area, precision['m']) + ' m²'
        }
      } else {
        area /= 0.836127
        if (area >= 3097600) {
          areaStr = GeometryUtil.formattedNumber(area / 3097600,
            precision['mi']) + ' mi²'
        } else if (area >= 4840) {
          areaStr = GeometryUtil.formattedNumber(area / 4840, precision['ac']) +
            ' acres'
        } else {
          areaStr = GeometryUtil.formattedNumber(area, precision['yd']) +
            ' yd²'
        }
      }
      return areaStr
    },
    readableDistance: function (distance, isMetric, isFeet, isNauticalMile,
                                precision) {
      var distanceStr
      var units
      var precision = Util.extend({}, defaultPrecision, precision)
      if (isMetric) {
        units = typeof isMetric === 'string'
          ? isMetric
          : 'metric'
      } else if (isFeet) {units = 'feet'} else if (isNauticalMile) {units = 'nauticalMile'} else {units = 'yards'}
      switch (units) {
        case'metric':
          if (distance > 1000) {
            distanceStr = GeometryUtil.formattedNumber(distance / 1000,
              precision['km']) + ' km'
          } else {
            distanceStr = GeometryUtil.formattedNumber(distance,
              precision['m']) + ' m'
          }
          break
        case'feet':
          distance *= 1.09361 * 3
          distanceStr = GeometryUtil.formattedNumber(distance,
            precision['ft']) + ' ft'
          break
        case'nauticalMile':
          distance *= 0.53996
          distanceStr = GeometryUtil.formattedNumber(distance / 1000,
            precision['nm']) + ' nm'
          break
        case'yards':
        default:
          distance *= 1.09361
          if (distance > 1760) {
            distanceStr = GeometryUtil.formattedNumber(distance / 1760,
              precision['mi']) + ' miles'
          } else {
            distanceStr = GeometryUtil.formattedNumber(distance,
              precision['yd']) + ' yd'
          }
          break
      }
      return distanceStr
    }
  })
  Util.extend(LineUtil, {
    segmentsIntersect: function (p, p1, p2, p3) {
      return this._checkCounterclockwise(p, p2, p3) !==
        this._checkCounterclockwise(p1, p2, p3) &&
        this._checkCounterclockwise(p, p1, p2) !==
        this._checkCounterclockwise(p, p1, p3)
    },
    _checkCounterclockwise: function (p, p1, p2) {
      return (p2.y - p.y) * (p1.x - p.x) > (p1.y - p.y) * (p2.x - p.x)
    }
  })
  Polyline.include({
    intersects: function () {
      var points = this._getProjectedPoints()
      var len = points ? points.length : 0
      var i
      var p
      var p1
      if (this._tooFewPointsForIntersection()) {return false}
      for (i = len - 1; i >= 3; i--) {
        p = points[i - 1]
        p1 = points[i]
        if (this._lineSegmentsIntersectsRange(p, p1, i - 2)) {return true}
      }
      return false
    },
    newLatLngIntersects: function (latlng, skipFirst) {
      if (!this._map) {return false}
      return this.newPointIntersects(this._map.latLngToLayerPoint(latlng),
        skipFirst)
    },
    newPointIntersects: function (newPoint, skipFirst) {
      var points = this._getProjectedPoints()
      var len = points ? points.length : 0
      var lastPoint = points ? points[len - 1] : null
      var maxIndex = len - 2
      if (this._tooFewPointsForIntersection(1)) {return false}
      return this._lineSegmentsIntersectsRange(lastPoint, newPoint, maxIndex,
        skipFirst ? 1 : 0)
    },
    _tooFewPointsForIntersection: function (extraPoints) {
      var points = this._getProjectedPoints()
      var len = points ? points.length : 0
      len += extraPoints || 0
      return !points || len <= 3
    },
    _lineSegmentsIntersectsRange: function (p, p1, maxIndex, minIndex) {
      var points = this._getProjectedPoints()
      var p2
      var p3
      minIndex = minIndex || 0
      for (var j = maxIndex; j > minIndex; j--) {
        p2 = points[j - 1]
        p3 = points[j]
        if (LineUtil.segmentsIntersect(p, p1, p2, p3)) {return true}
      }
      return false
    },
    _getProjectedPoints: function () {
      if (!this._defaultShape) {return this._originalPoints}
      var points = []
      var _shape = this._defaultShape()
      for (var i = 0; i < _shape.length; i++) {
        points.push(this._map.latLngToLayerPoint(_shape[i]))
      }
      return points
    }
  })
  Polygon.include({
    intersects: function () {
      var polylineIntersects
      var points = this._getProjectedPoints()
      var len
      var firstPoint
      var lastPoint
      var maxIndex
      if (this._tooFewPointsForIntersection()) {return false}
      polylineIntersects = Polyline.prototype.intersects.call(this)
      if (polylineIntersects) {return true}
      len = points.length
      firstPoint = points[0]
      lastPoint = points[len - 1]
      maxIndex = len - 2
      return this._lineSegmentsIntersectsRange(lastPoint, firstPoint, maxIndex,
        1)
    }
  })
  Map.mergeOptions({ drawControlTooltips: true })
  Draw.Tooltip = Class.extend({
    initialize: function (map) {
      this._map = map
      this._popupPane = map._panes.popupPane
      this._visible = false
      this._container = map.options.drawControlTooltips ? DomUtil.create('div',
        'winfo-draw-tooltip', this._popupPane) : null
      this._singleLineLabel = false
      this._map.on('mouseout', this._onMouseOut, this)
    },
    dispose: function () {
      this._map.off('mouseout', this._onMouseOut, this)
      if (this._container) {
        this._popupPane.removeChild(this._container)
        this._container = null
      }
    },
    updateContent: function (labelText) {
      if (!this._container) {return this}
      labelText.subtext = labelText.subtext || ''
      if (labelText.subtext.length === 0 && !this._singleLineLabel) {
        DomUtil.addClass(this._container, 'winfo-draw-tooltip-single')
        this._singleLineLabel = true
      } else if (labelText.subtext.length > 0 && this._singleLineLabel) {
        DomUtil.removeClass(this._container, 'winfo-draw-tooltip-single')
        this._singleLineLabel = false
      }
      this._container.innerHTML = (labelText.subtext.length > 0
        ? '<span class="winfo-draw-tooltip-subtext">' + labelText.subtext +
        '</span>' + '<br />'
        : '') + '<span>' + labelText.text + '</span>'
      if (!labelText.text && !labelText.subtext) {
        this._visible = false
        this._container.style.visibility = 'hidden'
      } else {
        this._visible = true
        this._container.style.visibility = 'inherit'
      }
      return this
    },
    updatePosition: function (latlng) {
      var pos = this._map.latLngToLayerPoint(latlng)
      var tooltipContainer = this._container
      if (this._container) {
        if (this._visible) {tooltipContainer.style.visibility = 'inherit'}
        DomUtil.setPosition(tooltipContainer, pos)
      }
      return this
    },
    showAsError: function () {
      if (this._container) {
        DomUtil.addClass(this._container, 'winfo-error-draw-tooltip')
      }
      return this
    },
    removeError: function () {
      if (this._container) {
        DomUtil.removeClass(this._container, 'winfo-error-draw-tooltip')
      }
      return this
    },
    _onMouseOut: function () {if (this._container) {this._container.style.visibility = 'hidden'}}
  })

  class NearbyElements {
    constructor (options) {
      this.nearByData = {}
      this.options = {
        drawToolType: 'circle',
        containsType: 'contains',
        dataSource: {},
        dataChangeCallBack: null
      }
      extend(this.options, options)
      this.setNearByKey()
      return this
    }

    addTo (map) {
      this._map = map
      return this
    }

    setDrawToolType (type) {
      this.options.drawToolType = type
      return this
    }

    setContainsType (type) {
      this.options.containsType = type
      return this
    }

    setDataSource (dataSource) {
      this.options.dataSource = dataSource
      return this
    }

    refreshData () {this.getNearByData()}

    setNearByKey () {
      for (let key in this.options.dataSource) {
        if (this.options.dataSource.hasOwnProperty(
          key)) {this.nearByData[key] = []}
      }
    }

    addCircle (latlng, options) {
      this._drawObj && this._drawObj.remove()
      this._drawObj = circle(latlng, options).addTo(this._map)
      this._drawObj.editing.enable()
      this.sourceTarget = this._drawObj
      this._drawObj.editing._shape.on('edit', data => {
        this.sourceTarget = data.sourceTarget
        this.getNearByData()
      })
      this.getNearByData()
    }

    openDrawTool () {
      this._drawObj && this._drawObj.remove()
      let drawOptions = {
        editable: true,
        events: {
          edit: (param) => {
            this.sourceTarget = param.sourceTarget
            this.getNearByData()
          },
          created: (param) => {
            this.sourceTarget = param.layer
            this.getNearByData()
          }
        }
      }
      switch (this.options.drawToolType) {
        case'circle':
          this._drawObj = new Draw.Circle(drawOptions).addTo(this._map)
          break
        case'polygon':
          this._drawObj = new Draw.Polygon(drawOptions).addTo(this._map)
          break
        case'rectangle':
          this._drawObj = new Draw.Rectangle(drawOptions).addTo(this._map)
          break
      }
      this._drawObj.enable()
    }

    closeDrawTool () {
      if (this._drawObj) {
        this._drawObj.editing && this._drawObj.editing.disable()
        this._drawObj.remove() && (this._drawObj = null)
      }
    }

    getNearByData () {
      for (let key in this.options.dataSource) {
        if (this.options.dataSource.hasOwnProperty(key)) {
          var containData = []
          if (key === 'canvasShip') {
            var canvasShipData = this.options.dataSource.canvasShip.getShapData()
            containData = this.containsHelp(canvasShipData)
          } else {
            containData = this.containsHelp(this.options.dataSource[key])
          }
          this.nearByData[key] = containData
        }
      }
      this.options.dataChangeCallBack &&
      this.options.dataChangeCallBack(this.nearByData)
      return this.nearByData
    }

    containsHelp (dataArr) {
      let containData = []
      dataArr.map(d => {if (this.containsShap(d.shap)) {containData.push(d)}})
      return containData
    }

    getPointByLatLng (latlng) {return this._map.latLngToLayerPoint(latlng)}

    containsShap (shap) {
      if (shap._latlng && shap._latlng.lat) {
        if (shap._radius) {
          let centerPoint = this.getPointByLatLng(shap._latlng)
          const _top = new Point(centerPoint.x, centerPoint.y + shap._radiusY)
          const _bottom = new Point(centerPoint.x,
            centerPoint.y - shap._radiusY)
          const _left = new Point(centerPoint.x - shap._radius, centerPoint.y)
          const _right = new Point(centerPoint.x - shap._radius, centerPoint.y)
          return this.contains([_top, _bottom, _left, _right])
        } else {return this.contains([this.getPointByLatLng(shap._latlng)])}
      }
      let shapLatLngs = []
      if (shap._latlngs) {
        if (shap._latlngs.length === 1) {shapLatLngs = shap._latlngs[0]}
        if (shap._latlngs.length > 1) {shapLatLngs = shap._latlngs}
        let pointByLatLng = []
        shapLatLngs.map(
          item => {pointByLatLng.push(this.getPointByLatLng(item))})
        return this.contains(pointByLatLng)
      }
      return false
    }

    contains (pointArr) {
      let result = null
      if (this.options.containsType === 'contains') {
        result = true
        pointArr.map(
          d => {if (!this.sourceTarget._containsPoint(d)) {result = false}})
      } else {
        result = false
        pointArr.map(
          d => {if (this.sourceTarget._containsPoint(d)) {result = true}})
      }
      return result
    }
  }
  let twoPointAngle = (p1, p2, direction) => {
    let lat1 = p1.lat / 180 * Math.PI
    let lat2 = p2.lat / 180 * Math.PI
    let lng1 = p1.lng / 180 * Math.PI
    let lng2 = p2.lng / 180 * Math.PI
    let y = Math.sin(lng2 - lng1) * Math.cos(lat2)
    let x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) *
      Math.cos(lng2 - lng1)
    let brng
    if (direction === 'inbound') {
      brng = (Math.atan2(y, x) * 180 / Math.PI + 180).toFixed(0)
    } else {brng = (Math.atan2(y, x) * 180 / Math.PI + 360).toFixed(0)}
    return (brng % 360)
  }
  let PolylineMeasure = Class.extend({
    options: {
      unit: 'metres',
      showBearings: true,
      bearingTextIn: '入角',
      bearingTextOut: '出角',
      distanceShowSameUnit: true,
      unitControlLabel: {
        metres: 'm',
        kilometres: 'km',
        feet: 'ft',
        landmiles: 'mi',
        nauticalmiles: 'nm'
      },
      _arcpoints: 100,
      tempLine: {
        color: '#00f',
        weight: 2,
        dashArray: '8,8'
      },
      fixedLine: {
        color: '#006',
        weight: 2
      },
      startCircle: {
        color: '#000',
        weight: 1,
        fillColor: '#0f0',
        fillOpacity: 1,
        radius: 3
      },
      intermedCircle: {
        color: '#000',
        weight: 1,
        fillColor: '#ff0',
        fillOpacity: 1,
        radius: 3
      },
      currentCircle: {
        color: '#000',
        weight: 1,
        fillColor: '#f0f',
        fillOpacity: 1,
        radius: 6
      },
      endCircle: {
        color: '#000',
        weight: 1,
        fillColor: '#f00',
        fillOpacity: 1,
        radius: 3
      },
      tooltipOption: { permanent: true }
    },
    number: 0,
    measureObject: {},
    initialize (options = {}) {
      extendOptions(this.options, options)
      return this
    },
    addTo (map) {
      this._map = map
      return this
    },
    onRemove () {},
    measure () {
      if (!this._measuring) {
        this._measuring = true
        this._mapdragging = false
        this._oldCursor = this._map._container.style.cursor
        this._map._container.style.cursor = 'crosshair'
        this._doubleClickZoom = this._map.doubleClickZoom.enabled()
        this._map.doubleClickZoom.disable()
        if (!this._layerPaint) {
          this._layerPaint = new LayerGroup().addTo(this._map)
        }
        this._map.on('mousemove', this._mouseMove, this)
        this._map.on('click', this._mouseClick, this)
      }
    },
    _mouseMove (e) {
      let mouseCoords = e.latlng
      let measureObject = this.measureObject[this.number]
      if (!mouseCoords || !measureObject) {return}
      this._map.on('dblclick', this.cancelPolyMease, this)
      measureObject.updateDashMeasure(e.latlng)
    },
    cancelPolyMease () {
      this._map._container.style.cursor = this._oldCursor
      this._map.off('mousemove', this._mouseMove, this)
      this._map.off('click', this._mouseClick, this)
      this._map.off('dblclick', this.cancelPolyMease, this)
      this.measureObject[this.number].overMeasure()
      this._measuring = false
      this.number++
    },
    removeIndex (index) {delete this.measureObject[index]},
    _mouseClick (e) {
      let measureObject = this.measureObject[this.number]
      if (!measureObject && !this._mapdragging) {
        this.initMeasureObject(e.latlng)
      } else {
        if (!this._mapdragging) {
          measureObject.addPoint(e.latlng)
        } else {this._mapdragging = false}
      }
    },
    initMeasureObject (latlng) {
      this.measureObject[this.number] = new MeasureLine({
        latlng,
        options: this.options,
        layer: this._layerPaint,
        id: this.number,
        removeIndex: (index) => {this.removeIndex(index)}
      })
    }
  })

  class MeasureLine {
    constructor ({ latlng, options, layer, id, removeIndex }) {
      this.id = id
      this.options = options
      this.layer = layer
      this.circleMarker = []
      this.allDistance = 0
      this.mouseToolTip = null
      this.polyline = []
      this.arrowMarker = []
      this.initStartPoint(latlng)
      this.initDashMeasure()
      this.removeIndex = removeIndex
      return this
    }

    initStartPoint (latlng) {
      this.dashStart = latlng
      this.addMarkerWithToolTip({
        latlng,
        style: this.options.startCircle
      })
    }

    _drawArrow (arcLine) {
      let midpoint = Math.round(arcLine.length / 2)
      if (midpoint < 2) return
      let P1 = arcLine[midpoint - 1]
      let P2 = arcLine[midpoint]
      let diffLng12 = P2[1] - P1[1]
      let diffLat12 = P2[0] - P1[0]
      let center = [P1[0] + diffLat12 / 2, P1[1] + diffLng12 / 2]
      let cssAngle = -Math.atan2(diffLat12, diffLng12) * 57.29578
      let iconArrow = new DivIcon({
        className: '',
        iconSize: [16, 16],
        iconAnchor: [8, 8],
        html: `<div style="font-size: 16px; line-height: 16px; vertical-align:top; transform: rotate(${cssAngle}deg)">&#x27a4;</div>`
      })
      let newArrowMarker = new Marker(center, {
        icon: iconArrow,
        zIndexOffset: -50
      }).addTo(this.layer)
      return newArrowMarker
    }

    _polylineArc (_from, _to) {
      function _GCinterpolate (f) {
        let A = Math.sin((1 - f) * d) / Math.sin(d)
        let B = Math.sin(f * d) / Math.sin(d)
        let x = A * Math.cos(fromLat) * Math.cos(fromLng) + B *
          Math.cos(toLat) * Math.cos(toLng)
        let y = A * Math.cos(fromLat) * Math.sin(fromLng) + B *
          Math.cos(toLat) * Math.sin(toLng)
        let z = A * Math.sin(fromLat) + B * Math.sin(toLat)
        let latInterpol = 180 / Math.PI *
          Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)))
        let lngInterpol = 180 / Math.PI * Math.atan2(y, x)
        let diff = lngInterpol - fromLng * 180 / Math.PI

        function trunc (n) {return Math[n > 0 ? 'floor' : 'ceil'](n)}

        if (diff < 0) {
          lngInterpol = lngInterpol - trunc((diff - 180) / 360) * 360
        } else {lngInterpol = lngInterpol - trunc((diff + 180) / 360) * 360}
        return [latInterpol, lngInterpol]
      }

      function _GCarc (npoints) {
        let arrArcCoords = []
        let delta = 1.0 / (npoints - 1)
        for (let i = 0; i < npoints; i++) {
          let step = delta * i
          let coordPair = _GCinterpolate(step)
          arrArcCoords.push(coordPair)
        }
        return arrArcCoords
      }

      let fromLat = _from.lat
      let fromLng = _from.lng
      let toLat = _to.lat
      let toLng = _to.lng
      fromLat = fromLat * Math.PI / 180
      fromLng = fromLng * Math.PI / 180
      toLat = toLat * Math.PI / 180
      toLng = toLng * Math.PI / 180
      let d = 2.0 * Math.asin(Math.sqrt(
        Math.pow(Math.sin((fromLat - toLat) / 2.0), 2) + Math.cos(fromLat) *
        Math.cos(toLat) * Math.pow(Math.sin((fromLng - toLng) / 2.0), 2)))
      let arrLatLngs
      if (d === 0) {
        arrLatLngs = [
          [
            fromLat,
            fromLng
          ]
        ]
      } else {arrLatLngs = _GCarc(this.options._arcpoints)}
      return arrLatLngs
    }

    addMarkerWithToolTip ({ latlng, style }) {
      let marker = circleMarker(latlng, style).addTo(this.layer)
      marker.inArc = '---'
      marker.out = '---'
      marker.distance = 0
      if (this.polyline.length) {
        marker.inArc = twoPointAngle(this.dashStart, latlng, 'inbound')
        marker.out = twoPointAngle(this.dashStart, latlng, 'outbound')
        marker.distance = this.dashStart.distanceTo(latlng)
        this.allDistance += marker.distance
      }
      marker.bindTooltip(``, this.options.tooltipOption)
      this.updateMarkerContent(marker)
      this.circleMarker.push(marker)
    }

    updateMarkerContent (marker) {
      marker.setTooltipContent(
        this.getToolTipTemplate(marker.inArc, marker.out, marker.distance,
          this.allDistance))
    }

    _getDistance (distance) {
      let dist = distance
      let unit
      if (this.options.unit === 'nauticalmiles') {
        unit = this.options.unitControlLabel.nauticalmiles
        if (dist >= 185200) {dist = (dist / 1852).toFixed(0)} else if (dist >=
          18520) {dist = (dist / 1852).toFixed(1)} else if (dist >= 1852) {
          dist = (dist / 1852).toFixed(2)
        } else {
          if (this.options.distanceShowSameUnit) {
            dist = (dist / 1852).toFixed(3)
          } else {
            dist = (dist / 0.3048).toFixed(0)
            unit = this.options.unitControlLabel.feet
          }
        }
      } else if (this.options.unit === 'landmiles') {
        unit = this.options.unitControlLabel.landmiles
        if (dist >= 160934.4) {
          dist = (dist / 1609.344).toFixed(0)
        } else if (dist >= 16093.44) {
          dist = (dist / 1609.344).toFixed(1)
        } else if (dist >= 1609.344) {
          dist = (dist / 1609.344).toFixed(2)
        } else {
          if (this.options.distanceShowSameUnit) {
            dist = (dist / 1609.344).toFixed(3)
          } else {
            dist = (dist / 0.3048).toFixed(0)
            unit = this.options.unitControlLabel.feet
          }
        }
      } else {
        unit = this.options.unitControlLabel.kilometres
        if (dist >= 100000) {dist = (dist / 1000).toFixed(0)} else if (dist >=
          10000) {dist = (dist / 1000).toFixed(1)} else if (dist >= 1000) {
          dist = (dist / 1000).toFixed(2)
        } else {
          if (this.options.distanceShowSameUnit) {
            dist = (dist / 1000).toFixed(3)
          } else {
            dist = (dist).toFixed(0)
            unit = this.options.unitControlLabel.metres
          }
        }
      }
      return {
        value: dist,
        unit: unit
      }
    }

    getToolTipTemplate (
      inArc, out, distance, allDistance) {
      let _distance = this._getDistance(distance)
      let _allDistance = this._getDistance(allDistance)
      return `${this.options.bearingTextIn}:${inArc}°|${this.options.bearingTextOut}:${out}°<br>线段:+${_distance.value}${_distance.unit}<br/>总长:${_allDistance.value}${_allDistance.unit}`
    }

    initDashMeasure () {
      this.dashMeasureLine = polyline([], this.options.tempLine).
      addTo(this.layer)
    }

    updateDashMeasure (latlng) {
      let arcPolyline = this._polylineArc(this.dashStart, latlng)
      this.dashMeasureLine.setLatLngs(arcPolyline)
      let angleIn = twoPointAngle(this.dashStart, latlng, 'inbound')
      let angleOut = twoPointAngle(this.dashStart, latlng, 'outbound')
      let distance = this.dashStart.distanceTo(latlng)
      if (!this.mouseToolTip) {
        this.mouseToolTip = tooltip(this.options.tooltipOption, this.layer)
      } else {
        let last = this.circleMarker[this.circleMarker.length - 1]
        last.out = angleOut
        this.updateMarkerContent(last)
      }
      this.mouseToolTip.setContent(
        this.getToolTipTemplate(angleIn, '---', distance,
          this.allDistance + distance))
      this.mouseToolTip.setLatLng(latlng).addTo(this.layer)
    }

    removeMouseTool () {
      this.mouseToolTip && this.mouseToolTip.remove()
      this.mouseToolTip = null
    }

    overMeasure () {
      this.removeMouseTool()
      let last = this.circleMarker.length - 1
      this.circleMarker[last].remove()
      this.circleMarker.splice(last, 1)
      last = this.circleMarker[this.circleMarker.length - 1]
      let template = this.getToolTipTemplate(last.inArc, last.out,
        last.distance, this.allDistance)
      last.closeTooltip()
      last.unbindTooltip(template)
      template = `<div class="content">${template}</div>`
      last.bindPopup(template, {
        autoClose: false,
        closeOnEscapeKey: false,
        className: `tooltip-popup measure measure-${this.id}`,
        closeOnClick: false
      })
      last.openPopup()
      last._popup.on('remove', () => {
        if (last._popup) {last._popup._close()}
        this.circleMarker.length && this.circleMarker.map(d => {
          d.closeTooltip()
          d.remove()
        })
        this.polyline.length && this.polyline.map(d => d.remove())
        this.polyline = []
        this.arrowMarker.length && this.arrowMarker.map(d => d && d.remove())
        this.dashMeasureLine.remove()
        this.dashMeasureLine = null
        this.arrowMarker = []
        this.circleMarker = []
        this.removeIndex(this.id)
        return this
      }, this)
    }

    addPoint (latlng) {
      this.removeMouseTool()
      let fixedLine = this.options.fixedLine
      let arcPolyline = this._polylineArc(this.dashStart, latlng)
      this.polyline.push(
        new Polyline(arcPolyline, fixedLine).addTo(this.layer))
      this.arrowMarker.push(this._drawArrow(arcPolyline))
      this.addMarkerWithToolTip({
        latlng,
        style: this.options.intermedCircle
      })
      this.dashStart = latlng
    }
  }

  let AdministrativeDivisionLayer = Layer.extend({
    options: {
      province: '156000000',
      BaseUrl: `basics-program/nav/list`,
      VTSParam: `parentId={province}`,
      requestCallback: false,
      style: {
        fillStyle: 'rgba(255,165,0, 0.6)',
        strokeStyle: 'rgba(255, 255, 255, 1)',
        lineWidth: 4,
        lineDash: [5, 5]
      },
      heightLineStyle: {
        fillStyle: 'rgba(255,165,0, 0.7)',
        strokeStyle: 'rgba(255, 0, 0, 0.8)',
        lineWidth: 3,
        lineDash: []
      }
    },
    _geoData: [],
    _tooltip: null,
    heightLineCode: null,
    initialize: function (options = {}) {extend(this.options, options)},
    onAdd: function () {},
    addTo: function (map) {
      this._map = map
      this._canvas = new Canvas().addTo(map)
      this._canvas._container.style.cursor = 'default'
      DomUtil.addClass(this._canvas._container, 'AdministrativeDivisionLayer')
      this._initEvent()
      this._getRegion(this.options.province)
      return this
    },
    removeToolTip () {
      this._map.hasLayer(this._tooltip) && this._map.removeLayer(this._tooltip)
    },
    onRemove: function () {
      this._offEvent()
      this._canvas.remove()
      this.removeToolTip()
      this._canvas = null
      this.options.geojson = []
    },
    _initEvent: function () {
      this._canvas.on('update', this.drawShap, this)
      this._map.on('zoomend', this._zoomEnd, this)
      this._map.on('click', this._onmouseClick, this)
      this._map.on('mousemove', this._onMouseMove, this)
    },
    _offEvent: function () {
      this._canvas.off('update', this.drawShap, this)
      this._map.off('zoomend', this._zoomEnd, this)
      this._map.off('click', this._onmouseClick, this)
      this._map.off('mousemove', this._onMouseMove, this)
    },
    _onmouseClick: function (e) {
      if (!this._geoData.length) return
      let layer = this._pointContainMouse(e.layerPoint)
      if (layer) {this._getRegion(layer.code)}
    },
    _onMouseMove (e) {
      this.removeToolTip()
      if (!this._geoData.length) return
      let layer = this._pointContainMouse(e.layerPoint)
      if (layer) {
        this.drawShap()
        this.heightLineCode = layer.code
        this._tooltip = new Tooltip()
        this._tooltip.setLatLng(layer.remark).
        addTo(this._map).
        setContent(layer.fullName)
      } else {
        if (this.heightLineCode) {
          this.heightLineCode = null
          this.drawShap()
        }
      }
    },
    _pointContainMouse (layerPoint) {
      for (let i = 0; i < this._geoData.length; i++) {
        let region = this._geoData[i].region
        for (let y = 0; y < region.length; y++) {
          let list = this.getPointArr(region[y])
          if (Util.isPointInPoly(layerPoint, list)) {return this._geoData[i]}
        }
      }
      return false
    },
    setRegionData: function (data) {this._getRegion(data)},
    _getRegion: function (province) {
      const Http = new XMLHttpRequest()
      const url = `${this.options.BaseUrl}?${this.options.VTSParam.replace(
        /{province}/, province)}`
      Http.onreadystatechange = (e) => {
        if (Http.readyState === 4 && Http.status === 200) {
          this.options.requestCallback && this.options.requestCallback(false)
          if (typeof Http.responseText === 'string') {
            this._canvas &&
            this._initLayer(JSON.parse(Http.responseText).datas)
          }
        }
      }
      Http.open('GET', url)
      Http.send()
      this.options.requestCallback && this.options.requestCallback(true)
    },
    _initLayer: function (res) {
      if (!(res && res.length)) return
      res.map(d => {
        d.region = JSON.parse(d.region)
        let region = []
        d.remark = d.remark.split(',').map(d => Number(d))
        d.region.length && d.region.map(list => {
          region.push(list.region.split(',').
          map(d => d.split(' ')).
          map(d => [Number(d[1]), Number(d[0])]))
        })
        d.region = region
      })
      this._geoData = res
      this.drawShap()
    },
    drawShap () {
      if (!this._canvas) return
      this._canvas._clearContent()
      let lastDraw = {}
      this._geoData.length && this._geoData.map(d => {
        if (d.code === this.heightLineCode) {lastDraw = d} else {
          this.drawRegion(d, this.options.style)
        }
      })
      if (lastDraw.region) {
        this.drawRegion(lastDraw, this.options.heightLineStyle)
      }
    },
    drawRegion (item, style) {
      item.region.length && item.region.map(list => {
        let pointArr = this.getPointArr(list)
        CanvasDrawTool.drawPolygonShap(this._canvas._ctx, {
          fillStyle: style.fillStyle,
          strokeStyle: style.strokeStyle,
          lineWidth: style.lineWidth,
          lineDash: style.lineDash,
          path: pointArr
        })
      })
    },
    getPoint (latlng) {return this._map.latLngToLayerPoint(latlng)},
    getPointArr (area) {
      let pointArr = []
      area.map(d => {pointArr.push(this.getPoint(d))})
      return pointArr
    },
    _zoomEnd (e) {
      let zoom = this._map.getZoom()
      if (zoom <= 6) {this._getRegion(this.options.province)}
    }
  })

  class CanvasMarker extends CanvasLayer {
    constructor (options = {}) {
      super()
      this.options = {
        data: [],
        icon: {
          url: null,
          size: [20, 20],
          rotate: 0
        },
        className: '',
        pointStyle: {
          fillStyle: 'rgba(255,100,0,1)',
          strokeStyle: 'yellow',
          lineWidth: 2,
          radius: 3
        },
        toolTip: {
          mode: 'mousemove',
          tooltipOptions: { direction: 'top' },
          delay: 300,
          teamplate: false
        },
        eventCallback: false,
        pointZoom: 7,
        zIndex: 111,
        showZoom: 5
      }
      this._tooltip = null
      this.setOptions(options)
      return this
    }

    addTo (map) {
      this.initIcon().then(res => {
        this.add(map, {
          zIndex: this.options.zIndex,
          className: this.options.className
        })
        this.drawShap()
      })
      return this
    }

    onRemove () {
      this.remove()
      this.removeToolTip()
      this.options.data = []
      return null
    }

    setData (data) {
      this.options.data = data
      this.drawShap()
    }

    initIcon () {
      return new Promise((resolve) => {
        if (this.options.icon.url) {
          this.getIcon([this.options.icon.url]).
          then(res => {
            this.options.icon.url = res[0]
            resolve()
          })
        } else {
          resolve()
        }
      })
    }

    _onMouseMove (e) {
      this._onMouseEvent(e)
    }

    _onMouseClick (e) {
      this._onMouseEvent(e)
    }

    reDraw (e) {
      this.drawShap()
    }

    drawShap () {
      this.removeToolTip()
      if (!this._canvas) return
      this._canvas._clearContent()
      if (this._map.getZoom() < this.options.showZoom) return
      if (!this.options.data.length) return
      if (this._map.getZoom() > this.options.pointZoom) {
        this.drawMarker()
      } else {
        this.drawPoint()
      }
    }

    drawPoint () {
      this.options.data.map(d => {
        let param = {
          path: [
            this._canvas.getLayerPoint([Number(d.lat), Number(d.lon)])
          ]
        }
        extend(param, this.options.pointStyle)
        CanvasDrawTool.drawPoint(this._canvas._ctx, param)
      })
    }

    drawMarker () {
      this.options.data.map(d => {
        d.icon = d.icon || {}
        let param = {
          width: (d.icon.size && d.icon.size[0]) || this.options.icon.size[0],
          height: (d.icon.size && d.icon.size[1]) || this.options.icon.size[1],
          rotate: d.icon.rotate || this.options.icon.rotate,
          path: [this._canvas.getLayerPoint([Number(d.lat), Number(d.lon)])],
          icon: d.icon.url || this.options.icon.url
        }
        CanvasDrawTool.drawImage(this._canvas._ctx, param)
      })
    }

    removeToolTip () {
      this._map.hasLayer(this._tooltip) && this._map.removeLayer(this._tooltip)
    }

    openToolTip (data, e) {
      this.removeToolTip()
      let tooltip = this._tooltip = new Tooltip(
        data.tooltipOptions || this.options.toolTip.tooltipOptions)
      tooltip.setLatLng([Number(data.lat), Number(data.lon)]).
      addTo(this._map).
      setContent(this.options.toolTip.teamplate(data, e))
    }

    _onMouseEvent (e) {

      this.removeToolTip()
      this.delayTimeout && clearTimeout(this.delayTimeout)
      let data = this._getShipContainMouse(e.layerPoint)
      this.setCursor('default')
      if (this._map.getZoom() <= this.options.pointZoom) return
      if (data) {
        this.setCursor('pointer')
        if ((e.type === this.options.toolTip.mode) &&
          this.options.toolTip.teamplate) {
          this.delayTimeout = setTimeout(() => {
            clearTimeout(this.delayTimeout)
            this.openToolTip(data, e)
          }, this.options.toolTip.delay)
        }
        this.options.eventCallback && this.options.eventCallback(data, e)
      }
    }

    _getShipContainMouse (containerPoint) {
      let length = this.options.data.length
      for (let i = 0; i < length; i++) {
        let d = this.options.data[i]
        let center = this._canvas.getLayerPoint([Number(d.lat), Number(d.lon)])
        let distance = center.distanceTo(containerPoint)
        if (distance <= this.options.icon.size[1] / 2) {return d}
      }
    }

    getIcon (imgUrl) {
      return new Promise((resolve) => {
        let request = []
        imgUrl.map(d => {
          request.push(new Promise((resolve) => {
            let icon = new Image()
            icon.src = d
            icon.onload = () => {resolve(icon)}
          }))
        })
        Promise.all(request).then(res => {resolve(res)})
      })
    }
  }

  Canvas.prototype.getLayerPoint = function (latlng) {
    return this._map.latLngToLayerPoint(latlng)
  }
  Canvas.prototype.getContainLatLon = function (point) {
    return this._map.containerPointToLatLng(point)
  }

  exports.TDTTK = TDTTK
  exports.PolylineMeasure = PolylineMeasure
  exports.GeometryUtil = GeometryUtil
  exports.CanvasDrawTool = CanvasDrawTool
  exports.version = version
  exports.Control = Control
  exports.control = control
  exports.Browser = Browser
  exports.Evented = Evented
  exports.Mixin = Mixin
  exports.Util = Util
  exports.Class = Class
  exports.Handler = Handler
  exports.extend = extend
  exports.bind = bind
  exports.stamp = stamp
  exports.setOptions = setOptions
  exports.extendOptions = extendOptions
  exports.DomEvent = DomEvent
  exports.DomUtil = DomUtil
  exports.PosAnimation = PosAnimation
  exports.Draggable = Draggable
  exports.LineUtil = LineUtil
  exports.PolyUtil = PolyUtil
  exports.Point = Point
  exports.point = toPoint
  exports.Bounds = Bounds
  exports.bounds = toBounds
  exports.Transformation = Transformation
  exports.transformation = toTransformation
  exports.Projection = index
  exports.LatLng = LatLng
  exports.latLng = toLatLng
  exports.LatLngBounds = LatLngBounds
  exports.latLngBounds = toLatLngBounds
  exports.CRS = CRS
  exports.GeoJSON = GeoJSON
  exports.geoJSON = geoJSON
  exports.geoJson = geoJson
  exports.Layer = Layer
  exports.LayerGroup = LayerGroup
  exports.layerGroup = layerGroup
  exports.FeatureGroup = FeatureGroup
  exports.featureGroup = featureGroup
  exports.ImageOverlay = ImageOverlay
  exports.imageOverlay = imageOverlay
  exports.VideoOverlay = VideoOverlay
  exports.videoOverlay = videoOverlay
  exports.SVGOverlay = SVGOverlay
  exports.svgOverlay = svgOverlay
  exports.DivOverlay = DivOverlay
  exports.Popup = Popup
  exports.popup = popup
  exports.Tooltip = Tooltip
  exports.tooltip = tooltip
  exports.Icon = Icon
  exports.icon = icon
  exports.DivIcon = DivIcon
  exports.divIcon = divIcon
  exports.Marker = Marker
  exports.marker = marker
  exports.TileLayer = TileLayer
  exports.tileLayer = tileLayer
  exports.GridLayer = GridLayer
  exports.gridLayer = gridLayer
  exports.SVG = SVG
  exports.svg = svg$1
  exports.Renderer = Renderer
  exports.Canvas = Canvas
  exports.canvas = canvas$1
  exports.Path = Path
  exports.CircleMarker = CircleMarker
  exports.circleMarker = circleMarker
  exports.Circle = Circle
  exports.circle = circle
  exports.Polyline = Polyline
  exports.polyline = polyline
  exports.Polygon = Polygon
  exports.polygon = polygon
  exports.Rectangle = Rectangle
  exports.rectangle = rectangle
  exports.Map = Map
  exports.map = createMap
  exports.LayerType = LayerType
  exports.TyphoonPath = TyphoonPath
  exports.CanvasShip = CanvasShip
  exports.LatLngGraticule = LatLngGraticule
  exports.VectorFlowLayer = VectorFlowLayer
  exports.FlowCanvasLayer = FlowCanvasLayer
  exports.VelocityLayer = VelocityLayer
  exports.HeatmapOverlay = HeatmapOverlay
  exports.TrackPlayBack = TrackPlayBack
  exports.TrackBack = TrackBack
  exports.Draw = Draw
  exports.Edit = Edit
  exports.NearbyElements = NearbyElements
  exports.AdministrativeDivisionLayer = AdministrativeDivisionLayer
  exports.WindCoilCircle = WindCoilCircle
  exports.fieldDiction = fieldDiction
  exports.Track = Track
  exports.Class = Class
  exports.canvasMarker = CanvasMarker
  exports.CanvasLayer = CanvasLayer
  var oldL = window.Winfo
  if (typeof define === 'function' && define.amd) {
    define('Winfo', [], function () {return exports})
  }
  exports.noConflict = function () {window.Winfo = oldL}
  window.Winfo = exports
  return exports
})
